@idlesummer/tasker 0.1.2 → 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 +19 -1
- 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,6 +1,6 @@
|
|
|
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!
|
|
@@ -12,6 +12,7 @@ Basically, it's a simple way to run tasks in sequence with nice terminal spinner
|
|
|
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
|
|
@@ -81,6 +82,7 @@ The `examples/` folder has working projects you can run:
|
|
|
81
82
|
|
|
82
83
|
- **[basic-pipeline](./examples/basic-pipeline)** - Super simple example to get started
|
|
83
84
|
- **[build-tool](./examples/build-tool)** - More realistic build tool with file operations
|
|
85
|
+
- **[conditional-tasks](./examples/conditional-tasks)** - Demonstrates conditional task execution
|
|
84
86
|
- **[formatters](./examples/formatters)** - Shows off all the formatting utilities
|
|
85
87
|
|
|
86
88
|
Each example is a standalone npm package. To run one:
|
|
@@ -125,6 +127,22 @@ const result = await pipeline.run({})
|
|
|
125
127
|
// result.duration is total time in ms
|
|
126
128
|
```
|
|
127
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
|
+
|
|
128
146
|
### Formatters
|
|
129
147
|
|
|
130
148
|
Make numbers pretty:
|
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"}
|