hudson 1.0.0
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/.hudson/hudson.yml +60 -0
- package/README.md +163 -0
- package/example/.hudson/hudson.yml +60 -0
- package/example/src/index.ts +1 -0
- package/example/tsconfig.json +21 -0
- package/package.json +32 -0
- package/src/commands/build.ts +187 -0
- package/src/commands/configure.ts +85 -0
- package/src/commands/dev.ts +114 -0
- package/src/commands/info.ts +78 -0
- package/src/commands/init.ts +80 -0
- package/src/commands/run.ts +114 -0
- package/src/commands/workspaces.ts +49 -0
- package/src/core/config.ts +214 -0
- package/src/core/runner.ts +108 -0
- package/src/core/workspace.ts +65 -0
- package/src/index.ts +138 -0
- package/src/types/gradient-string.d.ts +4 -0
- package/src/utils/display.ts +66 -0
- package/tsconfig.json +21 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { execa, ExecaError } from "execa";
|
|
2
|
+
import { HudsonConfig } from "./config.js";
|
|
3
|
+
import { log, formatDuration } from "../utils/display.js";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
|
|
6
|
+
export interface RunResult {
|
|
7
|
+
exitCode: number;
|
|
8
|
+
duration: number;
|
|
9
|
+
stdout: string;
|
|
10
|
+
stderr: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function runCommand(
|
|
14
|
+
cmd: string,
|
|
15
|
+
args: string[],
|
|
16
|
+
options: {
|
|
17
|
+
cwd?: string;
|
|
18
|
+
env?: Record<string, string>;
|
|
19
|
+
verbose?: boolean;
|
|
20
|
+
label?: string;
|
|
21
|
+
} = {}
|
|
22
|
+
): Promise<RunResult> {
|
|
23
|
+
const start = Date.now();
|
|
24
|
+
const label = options.label ?? `${cmd} ${args.join(" ")}`;
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const result = await execa(cmd, args, {
|
|
28
|
+
cwd: options.cwd ?? process.cwd(),
|
|
29
|
+
env: { ...process.env, ...options.env },
|
|
30
|
+
all: true,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const duration = Date.now() - start;
|
|
34
|
+
|
|
35
|
+
if (options.verbose && result.all) {
|
|
36
|
+
result.all.split("\n").forEach((line) => {
|
|
37
|
+
if (line.trim()) console.log(` ${chalk.dim(line)}`);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
exitCode: 0,
|
|
43
|
+
duration,
|
|
44
|
+
stdout: result.stdout,
|
|
45
|
+
stderr: result.stderr,
|
|
46
|
+
};
|
|
47
|
+
} catch (err) {
|
|
48
|
+
const duration = Date.now() - start;
|
|
49
|
+
const execaErr = err as ExecaError;
|
|
50
|
+
|
|
51
|
+
if (options.verbose && execaErr.all) {
|
|
52
|
+
execaErr.all.split("\n").forEach((line) => {
|
|
53
|
+
if (line.trim()) console.log(` ${chalk.dim(line)}`);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
exitCode: execaErr.exitCode ?? 1,
|
|
59
|
+
duration,
|
|
60
|
+
stdout: execaErr.stdout ?? "",
|
|
61
|
+
stderr: execaErr.stderr ?? "",
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export async function runScript(
|
|
67
|
+
scriptName: string,
|
|
68
|
+
config: HudsonConfig,
|
|
69
|
+
options: { cwd?: string; verbose?: boolean } = {}
|
|
70
|
+
): Promise<RunResult> {
|
|
71
|
+
const script = config.scripts[scriptName];
|
|
72
|
+
if (!script) {
|
|
73
|
+
return { exitCode: 1, duration: 0, stdout: "", stderr: `Script '${scriptName}' not found` };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const pm = config.packageManager;
|
|
77
|
+
const parts = script.split(" ");
|
|
78
|
+
const cmd = parts[0];
|
|
79
|
+
const args = parts.slice(1);
|
|
80
|
+
|
|
81
|
+
return runCommand(cmd, args, {
|
|
82
|
+
cwd: options.cwd ?? process.cwd(),
|
|
83
|
+
verbose: options.verbose ?? config.output.verbose,
|
|
84
|
+
label: script,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export async function runHook(
|
|
89
|
+
hookName: keyof HudsonConfig["hooks"],
|
|
90
|
+
config: HudsonConfig,
|
|
91
|
+
cwd = process.cwd()
|
|
92
|
+
): Promise<boolean> {
|
|
93
|
+
const hook = config.hooks[hookName];
|
|
94
|
+
if (!hook) return true;
|
|
95
|
+
|
|
96
|
+
log.step(`Running hook: ${hookName}`);
|
|
97
|
+
const parts = hook.split(" ");
|
|
98
|
+
const result = await runCommand(parts[0], parts.slice(1), {
|
|
99
|
+
cwd,
|
|
100
|
+
verbose: config.output.verbose,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
return result.exitCode === 0;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function getPackageManager(config: HudsonConfig): string {
|
|
107
|
+
return config.packageManager;
|
|
108
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import glob from "fast-glob";
|
|
4
|
+
import { HudsonConfig } from "./config.js";
|
|
5
|
+
|
|
6
|
+
export interface Workspace {
|
|
7
|
+
name: string;
|
|
8
|
+
path: string;
|
|
9
|
+
version: string;
|
|
10
|
+
scripts: Record<string, string>;
|
|
11
|
+
dependencies: Record<string, string>;
|
|
12
|
+
devDependencies: Record<string, string>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function discoverWorkspaces(
|
|
16
|
+
config: HudsonConfig,
|
|
17
|
+
cwd = process.cwd()
|
|
18
|
+
): Promise<Workspace[]> {
|
|
19
|
+
if (!config.workspaces.enabled) return [];
|
|
20
|
+
|
|
21
|
+
const patterns = config.workspaces.packages.map((p) => path.join(cwd, p, "package.json"));
|
|
22
|
+
const packageFiles = await glob(patterns, { absolute: true });
|
|
23
|
+
|
|
24
|
+
const workspaces: Workspace[] = [];
|
|
25
|
+
|
|
26
|
+
for (const pkgFile of packageFiles) {
|
|
27
|
+
if (!fs.existsSync(pkgFile)) continue;
|
|
28
|
+
try {
|
|
29
|
+
const pkg = JSON.parse(fs.readFileSync(pkgFile, "utf-8"));
|
|
30
|
+
workspaces.push({
|
|
31
|
+
name: pkg.name || path.basename(path.dirname(pkgFile)),
|
|
32
|
+
path: path.dirname(pkgFile),
|
|
33
|
+
version: pkg.version || "0.0.0",
|
|
34
|
+
scripts: pkg.scripts || {},
|
|
35
|
+
dependencies: pkg.dependencies || {},
|
|
36
|
+
devDependencies: pkg.devDependencies || {},
|
|
37
|
+
});
|
|
38
|
+
} catch {
|
|
39
|
+
// skip invalid package.json
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return workspaces;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function topologicalSort(workspaces: Workspace[]): Workspace[] {
|
|
47
|
+
const nameToWs = new Map(workspaces.map((w) => [w.name, w]));
|
|
48
|
+
const visited = new Set<string>();
|
|
49
|
+
const sorted: Workspace[] = [];
|
|
50
|
+
|
|
51
|
+
function visit(ws: Workspace) {
|
|
52
|
+
if (visited.has(ws.name)) return;
|
|
53
|
+
visited.add(ws.name);
|
|
54
|
+
|
|
55
|
+
const allDeps = { ...ws.dependencies, ...ws.devDependencies };
|
|
56
|
+
for (const dep of Object.keys(allDeps)) {
|
|
57
|
+
if (nameToWs.has(dep)) visit(nameToWs.get(dep)!);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
sorted.push(ws);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
for (const ws of workspaces) visit(ws);
|
|
64
|
+
return sorted;
|
|
65
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { program } from "commander";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import { HUDSON_VERSION, BANNER, log, symbols } from "./utils/display.js";
|
|
5
|
+
import { initCommand } from "./commands/init.js";
|
|
6
|
+
import { buildCommand } from "./commands/build.js";
|
|
7
|
+
import { devCommand } from "./commands/dev.js";
|
|
8
|
+
import { runScriptCommand } from "./commands/run.js";
|
|
9
|
+
import { configureCommand } from "./commands/configure.js";
|
|
10
|
+
import { infoCommand } from "./commands/info.js";
|
|
11
|
+
import { workspacesCommand } from "./commands/workspaces.js";
|
|
12
|
+
|
|
13
|
+
program
|
|
14
|
+
.name("hudson")
|
|
15
|
+
.description("Hudson โ The blazing-fast TypeScript build system")
|
|
16
|
+
.version(HUDSON_VERSION, "-v, --version", "Show Hudson version")
|
|
17
|
+
.addHelpText("beforeAll", BANNER());
|
|
18
|
+
|
|
19
|
+
program
|
|
20
|
+
.command("init")
|
|
21
|
+
.description("Initialize Hudson in the current project")
|
|
22
|
+
.option("-f, --force", "Reinitialize even if already set up")
|
|
23
|
+
.action(async (opts) => {
|
|
24
|
+
await initCommand({ force: opts.force });
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
program
|
|
28
|
+
.command("build")
|
|
29
|
+
.description("Build the project or workspace(s)")
|
|
30
|
+
.option("-w, --workspace <name>", "Build a specific workspace")
|
|
31
|
+
.option("--verbose", "Show detailed output")
|
|
32
|
+
.option("--no-clean", "Skip cleaning the output directory")
|
|
33
|
+
.option("--watch", "Watch for changes and rebuild")
|
|
34
|
+
.action(async (opts) => {
|
|
35
|
+
await buildCommand({
|
|
36
|
+
workspace: opts.workspace,
|
|
37
|
+
verbose: opts.verbose,
|
|
38
|
+
clean: opts.clean !== false,
|
|
39
|
+
watch: opts.watch,
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
program
|
|
44
|
+
.command("dev")
|
|
45
|
+
.description("Start development mode with file watching")
|
|
46
|
+
.option("--verbose", "Show detailed output")
|
|
47
|
+
.action(async (opts) => {
|
|
48
|
+
await devCommand({ verbose: opts.verbose });
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
program
|
|
52
|
+
.command("run <script>")
|
|
53
|
+
.description("Run a script defined in .hudson/hudson.yml")
|
|
54
|
+
.option("-w, --workspace <name>", "Run in a specific workspace")
|
|
55
|
+
.option("--all", "Run in all workspaces")
|
|
56
|
+
.option("--verbose", "Show detailed output")
|
|
57
|
+
.action(async (script, opts) => {
|
|
58
|
+
await runScriptCommand(script, {
|
|
59
|
+
workspace: opts.workspace,
|
|
60
|
+
all: opts.all,
|
|
61
|
+
verbose: opts.verbose,
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
program
|
|
66
|
+
.command("configure [key] [value]")
|
|
67
|
+
.description("Get or set a config value")
|
|
68
|
+
.option("-l, --list", "List all config values")
|
|
69
|
+
.option("-g, --get <key>", "Get a specific config value")
|
|
70
|
+
.option("--reset", "Reset config to defaults")
|
|
71
|
+
.action(async (key, value, opts) => {
|
|
72
|
+
await configureCommand(key, value, {
|
|
73
|
+
list: opts.list,
|
|
74
|
+
get: opts.get,
|
|
75
|
+
reset: opts.reset,
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
program
|
|
80
|
+
.command("info")
|
|
81
|
+
.description("Show project information and configuration")
|
|
82
|
+
.action(async () => {
|
|
83
|
+
await infoCommand();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
program
|
|
87
|
+
.command("workspaces [subcommand]")
|
|
88
|
+
.alias("ws")
|
|
89
|
+
.description("Manage workspaces (subcommands: list)")
|
|
90
|
+
.action(async (sub) => {
|
|
91
|
+
await workspacesCommand(sub);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Custom help formatting
|
|
95
|
+
program.configureOutput({
|
|
96
|
+
writeErr: (str) => {
|
|
97
|
+
console.log(` ${chalk.red("โ")} ${str.trim()}`);
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// Handle unknown commands
|
|
102
|
+
program.on("command:*", (ops) => {
|
|
103
|
+
console.log(BANNER());
|
|
104
|
+
log.error(`Unknown command: ${chalk.cyan(ops[0])}`);
|
|
105
|
+
log.blank();
|
|
106
|
+
log.step("Available commands:");
|
|
107
|
+
console.log(` ${chalk.cyan("hudson init")} Initialize Hudson in a project`);
|
|
108
|
+
console.log(` ${chalk.cyan("hudson build")} Build the project`);
|
|
109
|
+
console.log(` ${chalk.cyan("hudson dev")} Start dev mode with file watching`);
|
|
110
|
+
console.log(` ${chalk.cyan("hudson run <script>")} Run a project script`);
|
|
111
|
+
console.log(` ${chalk.cyan("hudson configure")} Get/set configuration values`);
|
|
112
|
+
console.log(` ${chalk.cyan("hudson info")} Show project information`);
|
|
113
|
+
console.log(` ${chalk.cyan("hudson workspaces")} Manage workspaces`);
|
|
114
|
+
log.blank();
|
|
115
|
+
process.exit(1);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
program.parse(process.argv);
|
|
119
|
+
|
|
120
|
+
// Show help if no command given
|
|
121
|
+
if (!process.argv.slice(2).length) {
|
|
122
|
+
console.log(BANNER());
|
|
123
|
+
console.log(` ${chalk.bold("Hudson")} โ The blazing-fast TypeScript build system`);
|
|
124
|
+
log.blank();
|
|
125
|
+
console.log(` ${chalk.dim("Usage:")} ${chalk.cyan("hudson <command> [options]")}`);
|
|
126
|
+
log.blank();
|
|
127
|
+
console.log(` ${chalk.dim("Commands:")}`);
|
|
128
|
+
console.log(` ${chalk.cyan("init")} Initialize Hudson in a project`);
|
|
129
|
+
console.log(` ${chalk.cyan("build")} Build the project`);
|
|
130
|
+
console.log(` ${chalk.cyan("dev")} Start dev mode with file watching`);
|
|
131
|
+
console.log(` ${chalk.cyan("run <script>")} Run a project script`);
|
|
132
|
+
console.log(` ${chalk.cyan("configure")} Get/set configuration values`);
|
|
133
|
+
console.log(` ${chalk.cyan("info")} Show project information`);
|
|
134
|
+
console.log(` ${chalk.cyan("ws")} Manage workspaces`);
|
|
135
|
+
log.blank();
|
|
136
|
+
console.log(` ${chalk.dim("Run")} ${chalk.cyan("hudson <command> --help")} ${chalk.dim("for command details")}`);
|
|
137
|
+
log.blank();
|
|
138
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import gradient from "gradient-string";
|
|
3
|
+
|
|
4
|
+
export const HUDSON_VERSION = "1.0.0";
|
|
5
|
+
|
|
6
|
+
// Hudson mascot - fishing on a lake (ASCII art)
|
|
7
|
+
export const MASCOT = `
|
|
8
|
+
${chalk.hex("#4FC3F7")("~")} ${chalk.hex("#FFB74D")("Hudson")} ${chalk.hex("#4FC3F7")("~")}
|
|
9
|
+
${chalk.hex("#81C784")(" ๐ฃ")}
|
|
10
|
+
${chalk.hex("#4FC3F7")(" โโโโโโโโโ")}
|
|
11
|
+
`;
|
|
12
|
+
|
|
13
|
+
export const MASCOT_FULL = `
|
|
14
|
+
${chalk.hex("#FFD54F")("( โข_โข)")} ${chalk.dim("Hudson Build System")}
|
|
15
|
+
${chalk.hex("#FFD54F")("( >๐ฃ")} ${chalk.dim("v" + HUDSON_VERSION)}
|
|
16
|
+
${chalk.hex("#FFD54F")("/ >")}
|
|
17
|
+
${chalk.hex("#4FC3F7")("โโโโโโโโโโโโโโโ")}
|
|
18
|
+
${chalk.hex("#4FC3F7")("โ โ โ โ โ โ โ โ")}
|
|
19
|
+
${chalk.hex("#1565C0")("~ ~ ~ ~ ~ ~ ~ ~")}
|
|
20
|
+
`;
|
|
21
|
+
|
|
22
|
+
export const BANNER = () => {
|
|
23
|
+
const title = gradient(["#4FC3F7", "#81C784", "#FFD54F"])(" HUDSON");
|
|
24
|
+
return `
|
|
25
|
+
${title} ${chalk.dim(`v${HUDSON_VERSION}`)} ${chalk.hex("#4FC3F7")("โต")}
|
|
26
|
+
${chalk.hex("#4FC3F7")(" โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ")}
|
|
27
|
+
`;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const symbols = {
|
|
31
|
+
success: chalk.green("โ"),
|
|
32
|
+
error: chalk.red("โ"),
|
|
33
|
+
warning: chalk.yellow("โ "),
|
|
34
|
+
info: chalk.cyan("โน"),
|
|
35
|
+
arrow: chalk.dim("โ"),
|
|
36
|
+
dot: chalk.dim("ยท"),
|
|
37
|
+
bullet: chalk.cyan("โ"),
|
|
38
|
+
fish: "๐",
|
|
39
|
+
hook: "๐ฃ",
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const log = {
|
|
43
|
+
success: (msg: string) => console.log(` ${symbols.success} ${chalk.green(msg)}`),
|
|
44
|
+
error: (msg: string) => console.log(` ${symbols.error} ${chalk.red(msg)}`),
|
|
45
|
+
warn: (msg: string) => console.log(` ${symbols.warning} ${chalk.yellow(msg)}`),
|
|
46
|
+
info: (msg: string) => console.log(` ${symbols.info} ${chalk.cyan(msg)}`),
|
|
47
|
+
step: (msg: string) => console.log(` ${symbols.arrow} ${chalk.white(msg)}`),
|
|
48
|
+
dim: (msg: string) => console.log(` ${chalk.dim(msg)}`),
|
|
49
|
+
blank: () => console.log(),
|
|
50
|
+
section: (title: string) => {
|
|
51
|
+
console.log();
|
|
52
|
+
console.log(` ${chalk.bold.hex("#FFD54F")(title)}`);
|
|
53
|
+
console.log(` ${chalk.dim("โ".repeat(40))}`);
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export function formatDuration(ms: number): string {
|
|
58
|
+
if (ms < 1000) return `${ms}ms`;
|
|
59
|
+
return `${(ms / 1000).toFixed(2)}s`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function formatSize(bytes: number): string {
|
|
63
|
+
if (bytes < 1024) return `${bytes}B`;
|
|
64
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
|
|
65
|
+
return `${(bytes / (1024 * 1024)).toFixed(2)}MB`;
|
|
66
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"declaration": true,
|
|
12
|
+
"declarationMap": true,
|
|
13
|
+
"sourceMap": true,
|
|
14
|
+
"resolveJsonModule": true,
|
|
15
|
+
"allowImportingTsExtensions": false,
|
|
16
|
+
"noUnusedLocals": false,
|
|
17
|
+
"noUnusedParameters": false
|
|
18
|
+
},
|
|
19
|
+
"include": ["src/**/*"],
|
|
20
|
+
"exclude": ["node_modules", "dist"]
|
|
21
|
+
}
|