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.
@@ -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,4 @@
1
+ declare module 'gradient-string' {
2
+ function gradient(colors: string[]): (text: string) => string;
3
+ export default gradient;
4
+ }
@@ -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
+ }