-
Notifications
You must be signed in to change notification settings - Fork 8
Expand file tree
/
Copy pathsaucebase.ts
More file actions
134 lines (115 loc) · 3.99 KB
/
saucebase.ts
File metadata and controls
134 lines (115 loc) · 3.99 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
#!/usr/bin/env tsx
import * as p from '@clack/prompts';
import { execSync, spawn } from 'child_process';
import pc from 'picocolors';
interface TaskDef {
name: string;
desc: string;
}
function loadTasks(): TaskDef[] {
try {
const json = execSync('task --list-all --json', {
encoding: 'utf-8',
stdio: ['pipe', 'pipe', 'pipe'],
});
const data = JSON.parse(json) as {
tasks: {
name: string;
desc: string;
location: { line: number; taskfile: string };
}[];
};
const rootTaskfile = data.tasks.find((t) => !t.name.includes(':'))
?.location.taskfile;
return data.tasks
.sort((a, b) => {
const aIsRoot = a.location.taskfile === rootTaskfile ? 0 : 1;
const bIsRoot = b.location.taskfile === rootTaskfile ? 0 : 1;
if (aIsRoot !== bIsRoot) return aIsRoot - bIsRoot;
if (a.location.taskfile !== b.location.taskfile)
return a.location.taskfile.localeCompare(
b.location.taskfile,
);
return a.location.line - b.location.line;
})
.map(({ name, desc }) => ({ name, desc }));
} catch {
p.log.error(
'Failed to load tasks from Taskfile. Is the task binary installed?',
);
process.exit(1);
}
}
const tasks = loadTasks();
// Build flat lookup of all tasks
const allTasks = new Map<string, TaskDef>();
for (const task of tasks) {
allTasks.set(task.name, task);
}
function runTask(taskName: string, args: string[]): Promise<number> {
return new Promise((resolve) => {
const child = spawn('task', [taskName, ...args], {
stdio: 'inherit',
env: { ...process.env },
});
child.on('close', (code) => resolve(code ?? 1));
child.on('error', (err) => {
if ((err as NodeJS.ErrnoException).code === 'ENOENT') {
p.log.error(
'task binary not found. Install: brew install go-task',
);
} else {
p.log.error(`Failed to run task: ${err.message}`);
}
resolve(1);
});
});
}
async function interactive(): Promise<void> {
const options = tasks.map((task) => ({
value: task.name,
label: `${pc.cyan(task.name.padEnd(12))} ${pc.dim(task.desc)}`,
}));
const selected = await p.select({
message: 'What would you like to do?',
options,
});
if (p.isCancel(selected)) {
p.cancel('Cancelled.');
process.exit(0);
}
p.log.step(`Running ${pc.cyan(selected as string)}...`);
const code = await runTask(selected as string, []);
if (code === 0) {
p.outro(pc.green('Done!'));
} else {
p.outro(pc.red(`Exited with code ${code}`));
process.exit(code);
}
}
async function direct(taskName: string, args: string[]): Promise<void> {
if (!allTasks.has(taskName)) {
// Still attempt — Taskfile may have tasks not listed in the menu
p.log.warn(`Unknown task ${pc.cyan(taskName)}, attempting anyway...`);
} else {
const task = allTasks.get(taskName)!;
p.log.step(`${pc.cyan(taskName)} ${pc.dim(`— ${task.desc}`)}`);
}
const code = await runTask(taskName, args);
if (code === 0) {
p.outro(pc.green('Done!'));
} else {
process.exit(code);
}
}
// ── Main ──────────────────────────────────────────────────────
const [taskName, ...rest] = process.argv.slice(2);
// Split rest into task args (after --)
const dashIndex = rest.indexOf('--');
const taskArgs = dashIndex >= 0 ? ['--', ...rest.slice(dashIndex + 1)] : rest;
p.intro(pc.bgCyan(pc.black(' Saucebase ')));
if (taskName) {
direct(taskName, taskArgs);
} else {
interactive();
}