@idlesummer/tasker 0.1.0 → 0.2.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/README.md +26 -10
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +2 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2 -0
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
# @idlesummer/tasker
|
|
2
2
|
|
|
3
|
-
A simple, lightweight task pipeline runner with CLI spinners and formatters for Node.js build tools.
|
|
3
|
+
A simple, lightweight declarative task pipeline runner with CLI spinners and formatters for Node.js build tools.
|
|
4
4
|
|
|
5
5
|
> **⚠️ Learning Project Notice**
|
|
6
6
|
> Hey! Just so you know, this is a learning project I built to understand task pipelines and build tools better. It works and I use it for my own stuff, but there might be bugs or rough edges. Feel free to use it, but maybe don't bet your production deploy on it just yet. Contributions and bug reports are super welcome though!
|
|
7
7
|
|
|
8
8
|
## What's This?
|
|
9
9
|
|
|
10
|
-
Basically, it's a simple way to run tasks in sequence with nice terminal spinners.
|
|
10
|
+
Basically, it's a simple way to run tasks in sequence with nice terminal spinners. The standard imperative way of writing build scripts seemed unelegant and I wanted something more functional/declarative, so I made this. Think of it like a mini task runner - simpler and lighter than Listr2 but more structured than a bash script.
|
|
11
11
|
|
|
12
12
|
## Features
|
|
13
13
|
|
|
14
14
|
- **Task Pipeline** - Run tasks one after another, each task gets the results from the previous ones
|
|
15
|
+
- **Conditional Tasks** - Use boolean expressions to conditionally include/exclude tasks
|
|
15
16
|
- **CLI Spinners** - Those satisfying loading spinners powered by ora
|
|
16
17
|
- **Formatters** - Helper functions to make bytes, durations, and file lists look nice
|
|
17
18
|
- **TypeScript** - Full type safety so you don't shoot yourself in the foot
|
|
@@ -56,7 +57,7 @@ That's it! You get:
|
|
|
56
57
|
|
|
57
58
|
## Why Would I Use This?
|
|
58
59
|
|
|
59
|
-
|
|
60
|
+
Use this if you:
|
|
60
61
|
- Want to build a simple CLI tool or build script
|
|
61
62
|
- Like seeing spinners while stuff happens
|
|
62
63
|
- Need tasks to share data between each other
|
|
@@ -69,8 +70,6 @@ Don't use this if you:
|
|
|
69
70
|
|
|
70
71
|
## Documentation
|
|
71
72
|
|
|
72
|
-
I wrote pretty detailed docs with a casual tone (because formal docs are boring):
|
|
73
|
-
|
|
74
73
|
- **[API Reference](./docs/API.md)** - All the functions and types explained
|
|
75
74
|
- **[Examples](./docs/EXAMPLES.md)** - Real code you can copy-paste
|
|
76
75
|
- **[Architecture](./docs/ARCHITECTURE.md)** - How it works under the hood
|
|
@@ -83,6 +82,7 @@ The `examples/` folder has working projects you can run:
|
|
|
83
82
|
|
|
84
83
|
- **[basic-pipeline](./examples/basic-pipeline)** - Super simple example to get started
|
|
85
84
|
- **[build-tool](./examples/build-tool)** - More realistic build tool with file operations
|
|
85
|
+
- **[conditional-tasks](./examples/conditional-tasks)** - Demonstrates conditional task execution
|
|
86
86
|
- **[formatters](./examples/formatters)** - Shows off all the formatting utilities
|
|
87
87
|
|
|
88
88
|
Each example is a standalone npm package. To run one:
|
|
@@ -113,12 +113,12 @@ Create a pipeline with tasks:
|
|
|
113
113
|
const pipeline = pipe([
|
|
114
114
|
{
|
|
115
115
|
name: 'Task name',
|
|
116
|
+
onSuccess: (ctx, duration) => 'Custom success message',
|
|
117
|
+
onError: (error) => 'Custom error message',
|
|
116
118
|
run: async (ctx) => {
|
|
117
119
|
// Do stuff
|
|
118
120
|
return { key: 'value' } // Updates context
|
|
119
121
|
},
|
|
120
|
-
onSuccess: (ctx, duration) => 'Custom success message',
|
|
121
|
-
onError: (error) => 'Custom error message'
|
|
122
122
|
}
|
|
123
123
|
])
|
|
124
124
|
|
|
@@ -127,6 +127,22 @@ const result = await pipeline.run({})
|
|
|
127
127
|
// result.duration is total time in ms
|
|
128
128
|
```
|
|
129
129
|
|
|
130
|
+
**Conditional Tasks:**
|
|
131
|
+
|
|
132
|
+
You can conditionally include tasks using boolean expressions:
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
const isProduction = process.env.NODE_ENV === 'production'
|
|
136
|
+
|
|
137
|
+
const pipeline = pipe([
|
|
138
|
+
{ name: 'Build', run: async () => ({ built: true }) },
|
|
139
|
+
isProduction && { name: 'Minify', run: async () => ({ minified: true }) },
|
|
140
|
+
{ name: 'Deploy', run: async () => ({ deployed: true }) },
|
|
141
|
+
])
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
When the condition is `false`, the task is automatically skipped. See the [conditional-tasks example](./examples/conditional-tasks) for a complete demo.
|
|
145
|
+
|
|
130
146
|
### Formatters
|
|
131
147
|
|
|
132
148
|
Make numbers pretty:
|
|
@@ -161,10 +177,10 @@ const build = pipe([
|
|
|
161
177
|
},
|
|
162
178
|
{
|
|
163
179
|
name: 'Compile TypeScript',
|
|
180
|
+
onSuccess: () => 'TypeScript compiled successfully',
|
|
164
181
|
run: async () => {
|
|
165
182
|
await execAsync('tsc')
|
|
166
183
|
},
|
|
167
|
-
onSuccess: () => 'TypeScript compiled successfully'
|
|
168
184
|
},
|
|
169
185
|
{
|
|
170
186
|
name: 'Show output',
|
|
@@ -194,7 +210,7 @@ These might get fixed eventually, or they might not. PRs welcome if you want to
|
|
|
194
210
|
|
|
195
211
|
## Contributing
|
|
196
212
|
|
|
197
|
-
Found a bug? Want to add a feature?
|
|
213
|
+
Found a bug? Want to add a feature?
|
|
198
214
|
|
|
199
215
|
1. Check [CONTRIBUTING.md](./docs/CONTRIBUTING.md) for guidelines
|
|
200
216
|
2. Open an issue or PR
|
|
@@ -204,7 +220,7 @@ Even if you're new to open source, feel free to contribute. I'm learning too!
|
|
|
204
220
|
|
|
205
221
|
## License
|
|
206
222
|
|
|
207
|
-
MIT -
|
|
223
|
+
MIT - See [LICENSE](LICENSE) file
|
|
208
224
|
|
|
209
225
|
## Questions?
|
|
210
226
|
|
package/dist/index.cjs
CHANGED
|
@@ -83,12 +83,14 @@ function fileList(baseDir, pattern = "**/*", width = 45) {
|
|
|
83
83
|
/**
|
|
84
84
|
* Creates a task pipeline that runs tasks sequentially with spinners
|
|
85
85
|
* @template TContext - The context type for the pipeline
|
|
86
|
+
* @param tasks - Array of tasks (supports conditional spreading with falsy values)
|
|
86
87
|
*/
|
|
87
88
|
function pipe(tasks) {
|
|
88
89
|
return { run: async (initialContext) => {
|
|
89
90
|
const startTime = Date.now();
|
|
90
91
|
let context = initialContext;
|
|
91
92
|
for (const task of tasks) {
|
|
93
|
+
if (!task) continue;
|
|
92
94
|
const taskStart = Date.now();
|
|
93
95
|
const spinner = (0, ora.default)(task.name).start();
|
|
94
96
|
try {
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["prettyBytes","prettyMs","path","pc","duration"],"sources":["../src/format.ts","../src/pipeline.ts"],"sourcesContent":["import { statSync } from 'fs'\r\nimport { join } from 'path'\r\nimport { fdir } from 'fdir'\r\nimport pc from 'picocolors'\r\nimport pm from 'picomatch'\r\nimport prettyBytes from 'pretty-bytes'\r\nimport prettyMs from 'pretty-ms'\r\n\r\n/**\r\n * Formats byte sizes into human-readable strings\r\n * @param size - The size in bytes\r\n * @returns Formatted string (e.g., \"1.23 kB\", \"4.56 MB\")\r\n */\r\nexport const bytes = prettyBytes\r\n\r\n/**\r\n * Formats milliseconds into human-readable durations\r\n * @param ms - The duration in milliseconds\r\n * @returns Formatted string (e.g., \"42ms\", \"1.2s\", \"2m 3.5s\")\r\n */\r\nexport const duration = prettyMs\r\n\r\n/**\r\n * Display a formatted list of files in a directory with their sizes\r\n * @param baseDir - The base directory to search\r\n * @param pattern - Glob pattern to match files\r\n * @param width - Row width for file paths\r\n * @returns Formatted file list with sizes and total\r\n */\r\nexport function fileList(baseDir: string, pattern = '**/*', width = 45) {\r\n // Find all files matching the pattern\r\n // Use windows: true option to handle both / and \\ as path separators\r\n const matcher = pm(pattern, { windows: true })\r\n const files = new fdir()\r\n .withRelativePaths()\r\n .filter(path => matcher(path))\r\n .crawl(baseDir)\r\n .sync()\r\n\r\n const stats = files.map(path => {\r\n const fullPath = join(baseDir, path)\r\n const displayPath = path.replace(/\\\\/g, '/')\r\n return {\r\n path: displayPath,\r\n size: statSync(fullPath).size,\r\n }\r\n })\r\n\r\n const total = stats.reduce((sum, stat) => sum + stat.size, 0)\r\n const lines = stats.map(({ path, size }) => {\r\n const paddedPath = pc.cyan(path.padEnd(width))\r\n const formattedSize = pc.dim(bytes(size))\r\n return ` ${paddedPath} ${formattedSize}`\r\n })\r\n\r\n const footerLabel = `${files.length} files, total:`.padEnd(width)\r\n const footer = ` ${pc.dim(footerLabel)} ${pc.dim(bytes(total))}`\r\n return [...lines, footer].join('\\n')\r\n}\r\n","import ora from 'ora'\r\n\r\n/** Base context type - extend this to add your own properties */\r\nexport type Context = Record<string, unknown>\r\n\r\n/**\r\n * A task that runs in a pipeline\r\n * @template TContext - The context type for this task\r\n * @property name - Task name shown in spinner\r\n * @property run - Task implementation - return updates to merge into context\r\n * @property onSuccess - Optional success message (default: task name)\r\n * @property onError - Optional error message (default: task name)\r\n */\r\nexport interface Task<TContext extends Context> {\r\n name: string\r\n run: (ctx: TContext) => Promise<Partial<TContext> | void>\r\n onSuccess?: (ctx: TContext, duration: number) => string\r\n onError?: (error: Error) => string\r\n}\r\n\r\n/**\r\n * Result after running a pipeline\r\n * @template TContext - The context type\r\n * @property context - Final context after all tasks\r\n * @property duration - Total duration in milliseconds\r\n */\r\nexport type PipeResult<TContext extends Context> = {\r\n context: TContext\r\n duration: number\r\n}\r\n\r\n/**\r\n * Creates a task pipeline that runs tasks sequentially with spinners\r\n * @template TContext - The context type for the pipeline\r\n */\r\nexport function pipe<TContext extends Context>(tasks: Task<TContext>[]) {\r\n return {\r\n run: async (initialContext: TContext) => {\r\n const startTime = Date.now()\r\n let context = initialContext\r\n\r\n for (const task of tasks) {\r\n const taskStart = Date.now()\r\n const spinner = ora(task.name).start()\r\n\r\n try {\r\n const updates = await task.run(context) ?? {}\r\n const duration = Date.now() - taskStart\r\n const message = task.onSuccess?.(context, duration) ?? task.name\r\n spinner.succeed(message)\r\n context = { ...context, ...updates }\r\n }\r\n catch (error) {\r\n const err = error instanceof Error ? error : new Error(String(error))\r\n const message = task.onError?.(err) ?? task.name\r\n spinner.fail(message)\r\n throw new Error(`Task \"${task.name}\" failed: ${err.message}`, { cause: err })\r\n }\r\n }\r\n\r\n const duration = Date.now() - startTime\r\n return { context, duration } as PipeResult<TContext>\r\n },\r\n }\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAaA,MAAa,QAAQA;;;;;;AAOrB,MAAa,WAAWC;;;;;;;;AASxB,SAAgB,SAAS,SAAiB,UAAU,QAAQ,QAAQ,IAAI;CAGtE,MAAM,iCAAa,SAAS,EAAE,SAAS,MAAM,CAAC;CAC9C,MAAM,QAAQ,IAAI,WAAM,CACrB,mBAAmB,CACnB,QAAO,WAAQ,QAAQC,OAAK,CAAC,CAC7B,MAAM,QAAQ,CACd,MAAM;CAET,MAAM,QAAQ,MAAM,KAAI,WAAQ;EAC9B,MAAM,0BAAgB,SAASA,OAAK;AAEpC,SAAO;GACL,MAFkBA,OAAK,QAAQ,OAAO,IAAI;GAG1C,uBAAe,SAAS,CAAC;GAC1B;GACD;CAEF,MAAM,QAAQ,MAAM,QAAQ,KAAK,SAAS,MAAM,KAAK,MAAM,EAAE;CAC7D,MAAM,QAAQ,MAAM,KAAK,EAAE,cAAM,WAAW;AAG1C,SAAO,KAFYC,mBAAG,KAAKD,OAAK,OAAO,MAAM,CAAC,CAEvB,IADDC,mBAAG,IAAI,MAAM,KAAK,CAAC;GAEzC;CAEF,MAAM,cAAc,GAAG,MAAM,OAAO,gBAAgB,OAAO,MAAM;CACjE,MAAM,SAAS,KAAKA,mBAAG,IAAI,YAAY,CAAC,IAAIA,mBAAG,IAAI,MAAM,MAAM,CAAC;AAChE,QAAO,CAAC,GAAG,OAAO,OAAO,CAAC,KAAK,KAAK
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["prettyBytes","prettyMs","path","pc","duration"],"sources":["../src/format.ts","../src/pipeline.ts"],"sourcesContent":["import { statSync } from 'fs'\r\nimport { join } from 'path'\r\nimport { fdir } from 'fdir'\r\nimport pc from 'picocolors'\r\nimport pm from 'picomatch'\r\nimport prettyBytes from 'pretty-bytes'\r\nimport prettyMs from 'pretty-ms'\r\n\r\n/**\r\n * Formats byte sizes into human-readable strings\r\n * @param size - The size in bytes\r\n * @returns Formatted string (e.g., \"1.23 kB\", \"4.56 MB\")\r\n */\r\nexport const bytes = prettyBytes\r\n\r\n/**\r\n * Formats milliseconds into human-readable durations\r\n * @param ms - The duration in milliseconds\r\n * @returns Formatted string (e.g., \"42ms\", \"1.2s\", \"2m 3.5s\")\r\n */\r\nexport const duration = prettyMs\r\n\r\n/**\r\n * Display a formatted list of files in a directory with their sizes\r\n * @param baseDir - The base directory to search\r\n * @param pattern - Glob pattern to match files\r\n * @param width - Row width for file paths\r\n * @returns Formatted file list with sizes and total\r\n */\r\nexport function fileList(baseDir: string, pattern = '**/*', width = 45) {\r\n // Find all files matching the pattern\r\n // Use windows: true option to handle both / and \\ as path separators\r\n const matcher = pm(pattern, { windows: true })\r\n const files = new fdir()\r\n .withRelativePaths()\r\n .filter(path => matcher(path))\r\n .crawl(baseDir)\r\n .sync()\r\n\r\n const stats = files.map(path => {\r\n const fullPath = join(baseDir, path)\r\n const displayPath = path.replace(/\\\\/g, '/')\r\n return {\r\n path: displayPath,\r\n size: statSync(fullPath).size,\r\n }\r\n })\r\n\r\n const total = stats.reduce((sum, stat) => sum + stat.size, 0)\r\n const lines = stats.map(({ path, size }) => {\r\n const paddedPath = pc.cyan(path.padEnd(width))\r\n const formattedSize = pc.dim(bytes(size))\r\n return ` ${paddedPath} ${formattedSize}`\r\n })\r\n\r\n const footerLabel = `${files.length} files, total:`.padEnd(width)\r\n const footer = ` ${pc.dim(footerLabel)} ${pc.dim(bytes(total))}`\r\n return [...lines, footer].join('\\n')\r\n}\r\n","import ora from 'ora'\r\n\r\n/** Base context type - extend this to add your own properties */\r\nexport type Context = Record<string, unknown>\r\n\r\n/**\r\n * A task that runs in a pipeline\r\n * @template TContext - The context type for this task\r\n * @property name - Task name shown in spinner\r\n * @property run - Task implementation - return updates to merge into context\r\n * @property onSuccess - Optional success message (default: task name)\r\n * @property onError - Optional error message (default: task name)\r\n */\r\nexport interface Task<TContext extends Context> {\r\n name: string\r\n run: (ctx: TContext) => Promise<Partial<TContext> | void>\r\n onSuccess?: (ctx: TContext, duration: number) => string\r\n onError?: (error: Error) => string\r\n}\r\n\r\n/**\r\n * Result after running a pipeline\r\n * @template TContext - The context type\r\n * @property context - Final context after all tasks\r\n * @property duration - Total duration in milliseconds\r\n */\r\nexport type PipeResult<TContext extends Context> = {\r\n context: TContext\r\n duration: number\r\n}\r\n\r\n/**\r\n * Creates a task pipeline that runs tasks sequentially with spinners\r\n * @template TContext - The context type for the pipeline\r\n * @param tasks - Array of tasks (supports conditional spreading with falsy values)\r\n */\r\nexport function pipe<TContext extends Context>(tasks: (Task<TContext> | false | null | undefined)[]) {\r\n return {\r\n run: async (initialContext: TContext) => {\r\n const startTime = Date.now()\r\n let context = initialContext\r\n\r\n for (const task of tasks) {\r\n // Skip falsy values (supports conditional spreading)\r\n if (!task) continue\r\n const taskStart = Date.now()\r\n const spinner = ora(task.name).start()\r\n\r\n try {\r\n const updates = await task.run(context) ?? {}\r\n const duration = Date.now() - taskStart\r\n const message = task.onSuccess?.(context, duration) ?? task.name\r\n spinner.succeed(message)\r\n context = { ...context, ...updates }\r\n }\r\n catch (error) {\r\n const err = error instanceof Error ? error : new Error(String(error))\r\n const message = task.onError?.(err) ?? task.name\r\n spinner.fail(message)\r\n throw new Error(`Task \"${task.name}\" failed: ${err.message}`, { cause: err })\r\n }\r\n }\r\n\r\n const duration = Date.now() - startTime\r\n return { context, duration } as PipeResult<TContext>\r\n },\r\n }\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAaA,MAAa,QAAQA;;;;;;AAOrB,MAAa,WAAWC;;;;;;;;AASxB,SAAgB,SAAS,SAAiB,UAAU,QAAQ,QAAQ,IAAI;CAGtE,MAAM,iCAAa,SAAS,EAAE,SAAS,MAAM,CAAC;CAC9C,MAAM,QAAQ,IAAI,WAAM,CACrB,mBAAmB,CACnB,QAAO,WAAQ,QAAQC,OAAK,CAAC,CAC7B,MAAM,QAAQ,CACd,MAAM;CAET,MAAM,QAAQ,MAAM,KAAI,WAAQ;EAC9B,MAAM,0BAAgB,SAASA,OAAK;AAEpC,SAAO;GACL,MAFkBA,OAAK,QAAQ,OAAO,IAAI;GAG1C,uBAAe,SAAS,CAAC;GAC1B;GACD;CAEF,MAAM,QAAQ,MAAM,QAAQ,KAAK,SAAS,MAAM,KAAK,MAAM,EAAE;CAC7D,MAAM,QAAQ,MAAM,KAAK,EAAE,cAAM,WAAW;AAG1C,SAAO,KAFYC,mBAAG,KAAKD,OAAK,OAAO,MAAM,CAAC,CAEvB,IADDC,mBAAG,IAAI,MAAM,KAAK,CAAC;GAEzC;CAEF,MAAM,cAAc,GAAG,MAAM,OAAO,gBAAgB,OAAO,MAAM;CACjE,MAAM,SAAS,KAAKA,mBAAG,IAAI,YAAY,CAAC,IAAIA,mBAAG,IAAI,MAAM,MAAM,CAAC;AAChE,QAAO,CAAC,GAAG,OAAO,OAAO,CAAC,KAAK,KAAK;;;;;;;;;;ACrBtC,SAAgB,KAA+B,OAAsD;AACnG,QAAO,EACL,KAAK,OAAO,mBAA6B;EACvC,MAAM,YAAY,KAAK,KAAK;EAC5B,IAAI,UAAU;AAEd,OAAK,MAAM,QAAQ,OAAO;AAExB,OAAI,CAAC,KAAM;GACX,MAAM,YAAY,KAAK,KAAK;GAC5B,MAAM,2BAAc,KAAK,KAAK,CAAC,OAAO;AAEtC,OAAI;IACF,MAAM,UAAU,MAAM,KAAK,IAAI,QAAQ,IAAI,EAAE;IAC7C,MAAMC,aAAW,KAAK,KAAK,GAAG;IAC9B,MAAM,UAAU,KAAK,YAAY,SAASA,WAAS,IAAI,KAAK;AAC5D,YAAQ,QAAQ,QAAQ;AACxB,cAAU;KAAE,GAAG;KAAS,GAAG;KAAS;YAE/B,OAAO;IACZ,MAAM,MAAM,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,MAAM,CAAC;IACrE,MAAM,UAAU,KAAK,UAAU,IAAI,IAAI,KAAK;AAC5C,YAAQ,KAAK,QAAQ;AACrB,UAAM,IAAI,MAAM,SAAS,KAAK,KAAK,YAAY,IAAI,WAAW,EAAE,OAAO,KAAK,CAAC;;;EAIjF,MAAMA,aAAW,KAAK,KAAK,GAAG;AAC9B,SAAO;GAAE;GAAS;GAAU;IAE/B"}
|
package/dist/index.d.cts
CHANGED
|
@@ -53,8 +53,9 @@ type PipeResult<TContext extends Context> = {
|
|
|
53
53
|
/**
|
|
54
54
|
* Creates a task pipeline that runs tasks sequentially with spinners
|
|
55
55
|
* @template TContext - The context type for the pipeline
|
|
56
|
+
* @param tasks - Array of tasks (supports conditional spreading with falsy values)
|
|
56
57
|
*/
|
|
57
|
-
declare function pipe<TContext extends Context>(tasks: Task<TContext>[]): {
|
|
58
|
+
declare function pipe<TContext extends Context>(tasks: (Task<TContext> | false | null | undefined)[]): {
|
|
58
59
|
run: (initialContext: TContext) => Promise<PipeResult<TContext>>;
|
|
59
60
|
};
|
|
60
61
|
//#endregion
|
package/dist/index.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/format.ts","../src/pipeline.ts"],"mappings":";;;;;AAaA;AAOA;AASA;;cAhBa,KAAA,SAAK,WAAA;AAAA;AAOlB;AASA;;;AAhBkB,cAOL,QAAA,SAAQ,QAAA;AAAA;AASrB;;;;AC1BA;AAUA;ADOqB,iBASL,QAAA,CAAA,OAAA,UAAA,OAAA,WAAA,KAAA;;;;KC1BJ,OAAA,GAAU,MAAA;AAAA;AAUtB;;;;;;;AAVsB,UAUL,IAAA,kBAAsB,OAAA;EAAA,IAAA;EAAA,GAAA,GAAA,GAAA,EAE1B,QAAA,KAAa,OAAA,CAAQ,OAAA,CAAQ,QAAA;EAAA,SAAA,IAAA,GAAA,EACtB,QAAA,EAAA,QAAA;EAAA,OAAA,IAAA,KAAA,EACA,KAAA;AAAA;AAAA;;AASpB;
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/format.ts","../src/pipeline.ts"],"mappings":";;;;;AAaA;AAOA;AASA;;cAhBa,KAAA,SAAK,WAAA;AAAA;AAOlB;AASA;;;AAhBkB,cAOL,QAAA,SAAQ,QAAA;AAAA;AASrB;;;;AC1BA;AAUA;ADOqB,iBASL,QAAA,CAAA,OAAA,UAAA,OAAA,WAAA,KAAA;;;;KC1BJ,OAAA,GAAU,MAAA;AAAA;AAUtB;;;;;;;AAVsB,UAUL,IAAA,kBAAsB,OAAA;EAAA,IAAA;EAAA,GAAA,GAAA,GAAA,EAE1B,QAAA,KAAa,OAAA,CAAQ,OAAA,CAAQ,QAAA;EAAA,SAAA,IAAA,GAAA,EACtB,QAAA,EAAA,QAAA;EAAA,OAAA,IAAA,KAAA,EACA,KAAA;AAAA;AAAA;;AASpB;AAUA;;;AAnBoB,KASR,UAAA,kBAA4B,OAAA;EAAA,OAAA,EAC7B,QAAA;EAAA,QAAA;AAAA;AAAA;AASX;;;;AATW,iBASK,IAAA,kBAAsB,OAAA,CAAA,CAAA,KAAA,GAAiB,IAAA,CAAK,QAAA;EAAA,GAAA,GAAA,cAAA,EAE5B,QAAA,KAAQ,OAAA,CAAA,UAAA,CAAA,QAAA;AAAA"}
|
package/dist/index.d.mts
CHANGED
|
@@ -53,8 +53,9 @@ type PipeResult<TContext extends Context> = {
|
|
|
53
53
|
/**
|
|
54
54
|
* Creates a task pipeline that runs tasks sequentially with spinners
|
|
55
55
|
* @template TContext - The context type for the pipeline
|
|
56
|
+
* @param tasks - Array of tasks (supports conditional spreading with falsy values)
|
|
56
57
|
*/
|
|
57
|
-
declare function pipe<TContext extends Context>(tasks: Task<TContext>[]): {
|
|
58
|
+
declare function pipe<TContext extends Context>(tasks: (Task<TContext> | false | null | undefined)[]): {
|
|
58
59
|
run: (initialContext: TContext) => Promise<PipeResult<TContext>>;
|
|
59
60
|
};
|
|
60
61
|
//#endregion
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/format.ts","../src/pipeline.ts"],"mappings":";;;;;AAaA;AAOA;AASA;;cAhBa,KAAA,SAAK,WAAA;AAAA;AAOlB;AASA;;;AAhBkB,cAOL,QAAA,SAAQ,QAAA;AAAA;AASrB;;;;AC1BA;AAUA;ADOqB,iBASL,QAAA,CAAA,OAAA,UAAA,OAAA,WAAA,KAAA;;;;KC1BJ,OAAA,GAAU,MAAA;AAAA;AAUtB;;;;;;;AAVsB,UAUL,IAAA,kBAAsB,OAAA;EAAA,IAAA;EAAA,GAAA,GAAA,GAAA,EAE1B,QAAA,KAAa,OAAA,CAAQ,OAAA,CAAQ,QAAA;EAAA,SAAA,IAAA,GAAA,EACtB,QAAA,EAAA,QAAA;EAAA,OAAA,IAAA,KAAA,EACA,KAAA;AAAA;AAAA;;AASpB;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/format.ts","../src/pipeline.ts"],"mappings":";;;;;AAaA;AAOA;AASA;;cAhBa,KAAA,SAAK,WAAA;AAAA;AAOlB;AASA;;;AAhBkB,cAOL,QAAA,SAAQ,QAAA;AAAA;AASrB;;;;AC1BA;AAUA;ADOqB,iBASL,QAAA,CAAA,OAAA,UAAA,OAAA,WAAA,KAAA;;;;KC1BJ,OAAA,GAAU,MAAA;AAAA;AAUtB;;;;;;;AAVsB,UAUL,IAAA,kBAAsB,OAAA;EAAA,IAAA;EAAA,GAAA,GAAA,GAAA,EAE1B,QAAA,KAAa,OAAA,CAAQ,OAAA,CAAQ,QAAA;EAAA,SAAA,IAAA,GAAA,EACtB,QAAA,EAAA,QAAA;EAAA,OAAA,IAAA,KAAA,EACA,KAAA;AAAA;AAAA;;AASpB;AAUA;;;AAnBoB,KASR,UAAA,kBAA4B,OAAA;EAAA,OAAA,EAC7B,QAAA;EAAA,QAAA;AAAA;AAAA;AASX;;;;AATW,iBASK,IAAA,kBAAsB,OAAA,CAAA,CAAA,KAAA,GAAiB,IAAA,CAAK,QAAA;EAAA,GAAA,GAAA,cAAA,EAE5B,QAAA,KAAQ,OAAA,CAAA,UAAA,CAAA,QAAA;AAAA"}
|
package/dist/index.mjs
CHANGED
|
@@ -51,12 +51,14 @@ function fileList(baseDir, pattern = "**/*", width = 45) {
|
|
|
51
51
|
/**
|
|
52
52
|
* Creates a task pipeline that runs tasks sequentially with spinners
|
|
53
53
|
* @template TContext - The context type for the pipeline
|
|
54
|
+
* @param tasks - Array of tasks (supports conditional spreading with falsy values)
|
|
54
55
|
*/
|
|
55
56
|
function pipe(tasks) {
|
|
56
57
|
return { run: async (initialContext) => {
|
|
57
58
|
const startTime = Date.now();
|
|
58
59
|
let context = initialContext;
|
|
59
60
|
for (const task of tasks) {
|
|
61
|
+
if (!task) continue;
|
|
60
62
|
const taskStart = Date.now();
|
|
61
63
|
const spinner = ora(task.name).start();
|
|
62
64
|
try {
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["duration"],"sources":["../src/format.ts","../src/pipeline.ts"],"sourcesContent":["import { statSync } from 'fs'\r\nimport { join } from 'path'\r\nimport { fdir } from 'fdir'\r\nimport pc from 'picocolors'\r\nimport pm from 'picomatch'\r\nimport prettyBytes from 'pretty-bytes'\r\nimport prettyMs from 'pretty-ms'\r\n\r\n/**\r\n * Formats byte sizes into human-readable strings\r\n * @param size - The size in bytes\r\n * @returns Formatted string (e.g., \"1.23 kB\", \"4.56 MB\")\r\n */\r\nexport const bytes = prettyBytes\r\n\r\n/**\r\n * Formats milliseconds into human-readable durations\r\n * @param ms - The duration in milliseconds\r\n * @returns Formatted string (e.g., \"42ms\", \"1.2s\", \"2m 3.5s\")\r\n */\r\nexport const duration = prettyMs\r\n\r\n/**\r\n * Display a formatted list of files in a directory with their sizes\r\n * @param baseDir - The base directory to search\r\n * @param pattern - Glob pattern to match files\r\n * @param width - Row width for file paths\r\n * @returns Formatted file list with sizes and total\r\n */\r\nexport function fileList(baseDir: string, pattern = '**/*', width = 45) {\r\n // Find all files matching the pattern\r\n // Use windows: true option to handle both / and \\ as path separators\r\n const matcher = pm(pattern, { windows: true })\r\n const files = new fdir()\r\n .withRelativePaths()\r\n .filter(path => matcher(path))\r\n .crawl(baseDir)\r\n .sync()\r\n\r\n const stats = files.map(path => {\r\n const fullPath = join(baseDir, path)\r\n const displayPath = path.replace(/\\\\/g, '/')\r\n return {\r\n path: displayPath,\r\n size: statSync(fullPath).size,\r\n }\r\n })\r\n\r\n const total = stats.reduce((sum, stat) => sum + stat.size, 0)\r\n const lines = stats.map(({ path, size }) => {\r\n const paddedPath = pc.cyan(path.padEnd(width))\r\n const formattedSize = pc.dim(bytes(size))\r\n return ` ${paddedPath} ${formattedSize}`\r\n })\r\n\r\n const footerLabel = `${files.length} files, total:`.padEnd(width)\r\n const footer = ` ${pc.dim(footerLabel)} ${pc.dim(bytes(total))}`\r\n return [...lines, footer].join('\\n')\r\n}\r\n","import ora from 'ora'\r\n\r\n/** Base context type - extend this to add your own properties */\r\nexport type Context = Record<string, unknown>\r\n\r\n/**\r\n * A task that runs in a pipeline\r\n * @template TContext - The context type for this task\r\n * @property name - Task name shown in spinner\r\n * @property run - Task implementation - return updates to merge into context\r\n * @property onSuccess - Optional success message (default: task name)\r\n * @property onError - Optional error message (default: task name)\r\n */\r\nexport interface Task<TContext extends Context> {\r\n name: string\r\n run: (ctx: TContext) => Promise<Partial<TContext> | void>\r\n onSuccess?: (ctx: TContext, duration: number) => string\r\n onError?: (error: Error) => string\r\n}\r\n\r\n/**\r\n * Result after running a pipeline\r\n * @template TContext - The context type\r\n * @property context - Final context after all tasks\r\n * @property duration - Total duration in milliseconds\r\n */\r\nexport type PipeResult<TContext extends Context> = {\r\n context: TContext\r\n duration: number\r\n}\r\n\r\n/**\r\n * Creates a task pipeline that runs tasks sequentially with spinners\r\n * @template TContext - The context type for the pipeline\r\n */\r\nexport function pipe<TContext extends Context>(tasks: Task<TContext>[]) {\r\n return {\r\n run: async (initialContext: TContext) => {\r\n const startTime = Date.now()\r\n let context = initialContext\r\n\r\n for (const task of tasks) {\r\n const taskStart = Date.now()\r\n const spinner = ora(task.name).start()\r\n\r\n try {\r\n const updates = await task.run(context) ?? {}\r\n const duration = Date.now() - taskStart\r\n const message = task.onSuccess?.(context, duration) ?? task.name\r\n spinner.succeed(message)\r\n context = { ...context, ...updates }\r\n }\r\n catch (error) {\r\n const err = error instanceof Error ? error : new Error(String(error))\r\n const message = task.onError?.(err) ?? task.name\r\n spinner.fail(message)\r\n throw new Error(`Task \"${task.name}\" failed: ${err.message}`, { cause: err })\r\n }\r\n }\r\n\r\n const duration = Date.now() - startTime\r\n return { context, duration } as PipeResult<TContext>\r\n },\r\n }\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;;AAaA,MAAa,QAAQ;;;;;;AAOrB,MAAa,WAAW;;;;;;;;AASxB,SAAgB,SAAS,SAAiB,UAAU,QAAQ,QAAQ,IAAI;CAGtE,MAAM,UAAU,GAAG,SAAS,EAAE,SAAS,MAAM,CAAC;CAC9C,MAAM,QAAQ,IAAI,MAAM,CACrB,mBAAmB,CACnB,QAAO,SAAQ,QAAQ,KAAK,CAAC,CAC7B,MAAM,QAAQ,CACd,MAAM;CAET,MAAM,QAAQ,MAAM,KAAI,SAAQ;EAC9B,MAAM,WAAW,KAAK,SAAS,KAAK;AAEpC,SAAO;GACL,MAFkB,KAAK,QAAQ,OAAO,IAAI;GAG1C,MAAM,SAAS,SAAS,CAAC;GAC1B;GACD;CAEF,MAAM,QAAQ,MAAM,QAAQ,KAAK,SAAS,MAAM,KAAK,MAAM,EAAE;CAC7D,MAAM,QAAQ,MAAM,KAAK,EAAE,MAAM,WAAW;AAG1C,SAAO,KAFY,GAAG,KAAK,KAAK,OAAO,MAAM,CAAC,CAEvB,IADD,GAAG,IAAI,MAAM,KAAK,CAAC;GAEzC;CAEF,MAAM,cAAc,GAAG,MAAM,OAAO,gBAAgB,OAAO,MAAM;CACjE,MAAM,SAAS,KAAK,GAAG,IAAI,YAAY,CAAC,IAAI,GAAG,IAAI,MAAM,MAAM,CAAC;AAChE,QAAO,CAAC,GAAG,OAAO,OAAO,CAAC,KAAK,KAAK
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["duration"],"sources":["../src/format.ts","../src/pipeline.ts"],"sourcesContent":["import { statSync } from 'fs'\r\nimport { join } from 'path'\r\nimport { fdir } from 'fdir'\r\nimport pc from 'picocolors'\r\nimport pm from 'picomatch'\r\nimport prettyBytes from 'pretty-bytes'\r\nimport prettyMs from 'pretty-ms'\r\n\r\n/**\r\n * Formats byte sizes into human-readable strings\r\n * @param size - The size in bytes\r\n * @returns Formatted string (e.g., \"1.23 kB\", \"4.56 MB\")\r\n */\r\nexport const bytes = prettyBytes\r\n\r\n/**\r\n * Formats milliseconds into human-readable durations\r\n * @param ms - The duration in milliseconds\r\n * @returns Formatted string (e.g., \"42ms\", \"1.2s\", \"2m 3.5s\")\r\n */\r\nexport const duration = prettyMs\r\n\r\n/**\r\n * Display a formatted list of files in a directory with their sizes\r\n * @param baseDir - The base directory to search\r\n * @param pattern - Glob pattern to match files\r\n * @param width - Row width for file paths\r\n * @returns Formatted file list with sizes and total\r\n */\r\nexport function fileList(baseDir: string, pattern = '**/*', width = 45) {\r\n // Find all files matching the pattern\r\n // Use windows: true option to handle both / and \\ as path separators\r\n const matcher = pm(pattern, { windows: true })\r\n const files = new fdir()\r\n .withRelativePaths()\r\n .filter(path => matcher(path))\r\n .crawl(baseDir)\r\n .sync()\r\n\r\n const stats = files.map(path => {\r\n const fullPath = join(baseDir, path)\r\n const displayPath = path.replace(/\\\\/g, '/')\r\n return {\r\n path: displayPath,\r\n size: statSync(fullPath).size,\r\n }\r\n })\r\n\r\n const total = stats.reduce((sum, stat) => sum + stat.size, 0)\r\n const lines = stats.map(({ path, size }) => {\r\n const paddedPath = pc.cyan(path.padEnd(width))\r\n const formattedSize = pc.dim(bytes(size))\r\n return ` ${paddedPath} ${formattedSize}`\r\n })\r\n\r\n const footerLabel = `${files.length} files, total:`.padEnd(width)\r\n const footer = ` ${pc.dim(footerLabel)} ${pc.dim(bytes(total))}`\r\n return [...lines, footer].join('\\n')\r\n}\r\n","import ora from 'ora'\r\n\r\n/** Base context type - extend this to add your own properties */\r\nexport type Context = Record<string, unknown>\r\n\r\n/**\r\n * A task that runs in a pipeline\r\n * @template TContext - The context type for this task\r\n * @property name - Task name shown in spinner\r\n * @property run - Task implementation - return updates to merge into context\r\n * @property onSuccess - Optional success message (default: task name)\r\n * @property onError - Optional error message (default: task name)\r\n */\r\nexport interface Task<TContext extends Context> {\r\n name: string\r\n run: (ctx: TContext) => Promise<Partial<TContext> | void>\r\n onSuccess?: (ctx: TContext, duration: number) => string\r\n onError?: (error: Error) => string\r\n}\r\n\r\n/**\r\n * Result after running a pipeline\r\n * @template TContext - The context type\r\n * @property context - Final context after all tasks\r\n * @property duration - Total duration in milliseconds\r\n */\r\nexport type PipeResult<TContext extends Context> = {\r\n context: TContext\r\n duration: number\r\n}\r\n\r\n/**\r\n * Creates a task pipeline that runs tasks sequentially with spinners\r\n * @template TContext - The context type for the pipeline\r\n * @param tasks - Array of tasks (supports conditional spreading with falsy values)\r\n */\r\nexport function pipe<TContext extends Context>(tasks: (Task<TContext> | false | null | undefined)[]) {\r\n return {\r\n run: async (initialContext: TContext) => {\r\n const startTime = Date.now()\r\n let context = initialContext\r\n\r\n for (const task of tasks) {\r\n // Skip falsy values (supports conditional spreading)\r\n if (!task) continue\r\n const taskStart = Date.now()\r\n const spinner = ora(task.name).start()\r\n\r\n try {\r\n const updates = await task.run(context) ?? {}\r\n const duration = Date.now() - taskStart\r\n const message = task.onSuccess?.(context, duration) ?? task.name\r\n spinner.succeed(message)\r\n context = { ...context, ...updates }\r\n }\r\n catch (error) {\r\n const err = error instanceof Error ? error : new Error(String(error))\r\n const message = task.onError?.(err) ?? task.name\r\n spinner.fail(message)\r\n throw new Error(`Task \"${task.name}\" failed: ${err.message}`, { cause: err })\r\n }\r\n }\r\n\r\n const duration = Date.now() - startTime\r\n return { context, duration } as PipeResult<TContext>\r\n },\r\n }\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;;AAaA,MAAa,QAAQ;;;;;;AAOrB,MAAa,WAAW;;;;;;;;AASxB,SAAgB,SAAS,SAAiB,UAAU,QAAQ,QAAQ,IAAI;CAGtE,MAAM,UAAU,GAAG,SAAS,EAAE,SAAS,MAAM,CAAC;CAC9C,MAAM,QAAQ,IAAI,MAAM,CACrB,mBAAmB,CACnB,QAAO,SAAQ,QAAQ,KAAK,CAAC,CAC7B,MAAM,QAAQ,CACd,MAAM;CAET,MAAM,QAAQ,MAAM,KAAI,SAAQ;EAC9B,MAAM,WAAW,KAAK,SAAS,KAAK;AAEpC,SAAO;GACL,MAFkB,KAAK,QAAQ,OAAO,IAAI;GAG1C,MAAM,SAAS,SAAS,CAAC;GAC1B;GACD;CAEF,MAAM,QAAQ,MAAM,QAAQ,KAAK,SAAS,MAAM,KAAK,MAAM,EAAE;CAC7D,MAAM,QAAQ,MAAM,KAAK,EAAE,MAAM,WAAW;AAG1C,SAAO,KAFY,GAAG,KAAK,KAAK,OAAO,MAAM,CAAC,CAEvB,IADD,GAAG,IAAI,MAAM,KAAK,CAAC;GAEzC;CAEF,MAAM,cAAc,GAAG,MAAM,OAAO,gBAAgB,OAAO,MAAM;CACjE,MAAM,SAAS,KAAK,GAAG,IAAI,YAAY,CAAC,IAAI,GAAG,IAAI,MAAM,MAAM,CAAC;AAChE,QAAO,CAAC,GAAG,OAAO,OAAO,CAAC,KAAK,KAAK;;;;;;;;;;ACrBtC,SAAgB,KAA+B,OAAsD;AACnG,QAAO,EACL,KAAK,OAAO,mBAA6B;EACvC,MAAM,YAAY,KAAK,KAAK;EAC5B,IAAI,UAAU;AAEd,OAAK,MAAM,QAAQ,OAAO;AAExB,OAAI,CAAC,KAAM;GACX,MAAM,YAAY,KAAK,KAAK;GAC5B,MAAM,UAAU,IAAI,KAAK,KAAK,CAAC,OAAO;AAEtC,OAAI;IACF,MAAM,UAAU,MAAM,KAAK,IAAI,QAAQ,IAAI,EAAE;IAC7C,MAAMA,aAAW,KAAK,KAAK,GAAG;IAC9B,MAAM,UAAU,KAAK,YAAY,SAASA,WAAS,IAAI,KAAK;AAC5D,YAAQ,QAAQ,QAAQ;AACxB,cAAU;KAAE,GAAG;KAAS,GAAG;KAAS;YAE/B,OAAO;IACZ,MAAM,MAAM,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,MAAM,CAAC;IACrE,MAAM,UAAU,KAAK,UAAU,IAAI,IAAI,KAAK;AAC5C,YAAQ,KAAK,QAAQ;AACrB,UAAM,IAAI,MAAM,SAAS,KAAK,KAAK,YAAY,IAAI,WAAW,EAAE,OAAO,KAAK,CAAC;;;EAIjF,MAAMA,aAAW,KAAK,KAAK,GAAG;AAC9B,SAAO;GAAE;GAAS;GAAU;IAE/B"}
|