# @canonical/task 中文文档

规范地址：https://npmcn.com/package/%40canonical/task/

版本：0\.44\.0
npm 元数据更新时间：2026-10-23T01:41:24.988Z
中文译文更新时间：2026-11-09T13:06:17.856Z
软件包许可：LGPL-3\.0

本文由上游 README 翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/%40canonical/task
原文对照：https://npmcn.com/package/%40canonical/task/?lang=original
项目主页：https://github.com/canonical/pragma-core#readme
源码仓库：https://github.com/canonical/pragma-core

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：309 kB，67 个文件
- 模块格式：ES Module
- TypeScript 类型：包内自带
- 运行依赖：0 个
- 首次发布：2026-03-26T12:22:35.567Z
- 最新版本发布：2026-10-23T01:41:24.609Z
- 近 30 天下载量：2662（2026-10-09 至 2026-11-07，npm 官方下载统计）
- 已发布版本数：27（近 12 个月 27 个）

## 中文简介

用于可相容,可测试,可干燥的CLI操作的一元效应框架.

## 安装

```sh
npm install @canonical/task
```

也可以使用：`pnpm add @canonical/task`、`yarn add @canonical/task`、`bun add @canonical/task`

## README

# @ canonical/ 任务

用于可相容,可测试,可干燥的CLI操作的一元效应框架.

任务是对计算过程的纯描述——在解释之前不会执行. 这使得您可以测试文件系统操作, shell 命令, 和用户提示而不触摸真实的 I/O.

## 安装

```bash
bun add @canonical/task
```

## 入境点

货包装了两个入口,按它们能达到的程度分成:

-   **`@canonical/task`** ——无节点基础:核心类型,任务僧尼,效果构建器,原始人,梳理人,干跑/测试翻译(`dryRun`, (中文(简体) ), `dryRunWith`, (中文(简体) ), `collectEffects`,...),撤销收藏(`collectUndos`),以及 `TaskExecutionError`. 所构建的节点可以从本条目的导入图(由关闭行走测试所支撑)得到,因此它为浏览器和边缘目标打包干净. 所有关于 _说明_, (中文(简体) ), _编译_,以及 _预览_ 工作住在这里.
-   **`@canonical/task/node`** - 节点触控口译员: `runTask` 编号 : `run` 编号 : `executeEffect` (执行文件系统、执行和即时效果,导入) `node:fs/promises`, (中文(简体) ), `node:path`,以及 `node:child_process`和 `runUndo`。只是实际上 _运行_ 针对主机的任务需要此条目.

因为模块解析发生在树摇之前, 拆分事项是: 如果口译员住在基项中, 导入一个纯函数就会拉动 `node:` 导入到模块图中并失败任何浏览器-平台的构建——无论捆绑实际使用什么.

## 快速启动

```typescript
import { gen, $, writeFile, readFile, info } from "@canonical/task";
import { runTask } from "@canonical/task/node";

const setup = gen(function* () {
  const name = yield* $(readFile("name.txt"));
  yield* $(writeFile("greeting.txt", `Hello, ${name}!`));
  yield* $(info("Done"));
});

await runTask(setup);
```

## 核心概念

### 任务修饰

A 类 `Task<A>` 有三样东西:

-   **纯** - 完成的计算持有一个数值
-   **效果** - 附带效应说明
-   **失败** ——计算失败,结构错误

任务由 `flatMap` 或者,在工学上,使用生成语法:

```typescript
import { flatMap, readFile, writeFile } from "@canonical/task";

// flatMap style
const greet = flatMap(
  readFile("name.txt"),
  (name) => writeFile("greeting.txt", `Hello, ${name}!`),
);
```

### 生成语法

这个 `gen` 编号 : `$` 对子允许您不嵌入而写入顺序效果代码 `flatMap` 锁链。 使用 `yield*` 与 `$(task)` 打开任务并获得其值 :

```typescript
import { gen, $, readFile, writeFile, info, exists } from "@canonical/task";

const migrate = gen(function* () {
  const raw = yield* $(readFile("config.json"));
  const config = JSON.parse(raw);

  config.version = 2;

  yield* $(writeFile("config.json", JSON.stringify(config, null, 2)));
  yield* $(info(`Migrated to v${config.version}`));

  return config;
});
```

在引擎盖下, `gen` 编曲 `flatMap` 调用——任务在解释前仍然是纯数据结构. 使用您喜欢的样式; `gen` 建议任何超过两到三个步骤.

### 效果

效果是纯数据——贴有标签的工会描述应该发生什么:

| 类别 | 效果 |
| --- | --- |
| **文件一/O** | `ReadFile`, (中文(简体) ), `WriteFile`, (中文(简体) ), `AppendFile`, (中文(简体) ), `CopyFile`, (中文(简体) ), `CopyDirectory`, (中文(简体) ), `DeleteFile`, (中文(简体) ), `DeleteDirectory`, (中文(简体) ), `MakeDir`, (中文(简体) ), `Exists`, (中文(简体) ), `Glob`, (中文(简体) ), `Symlink` |
| **进程** | `Exec` |
| **互动** | `Prompt` (文本、确认、选择、多选择) |
| **日志** | `Log` (调试,信息,警告,出错) |
| **A. 背景情况** | `ReadContext`, (中文(简体) ), `WriteContext` |
| **货币** | `Parallel`, (中文(简体) ), `Race` |

### 初级

任务返回包装符,用于每种效果。 这些是你最常用的构件:

```typescript
import {
  readFile, writeFile, appendFile, copyFile, copyDirectory,
  deleteFile, deleteDirectory, mkdir, exists, symlink, glob,
  exec, execSimple,
  prompt, promptText, promptConfirm, promptSelect, promptMultiselect,
  log, debug, info, warn, error,
  getContext, setContext, withContext,
  noop, succeed,
} from "@canonical/task";
```

#### 文件系统

```typescript
const content = yield* $(readFile("src/index.ts"));
yield* $(writeFile("dist/index.js", compiled));
yield* $(appendFile("log.txt", `Built at ${Date.now()}\n`));
yield* $(copyFile("template.json", "output/package.json"));
yield* $(copyDirectory("templates/", "output/"));
yield* $(mkdir("output/lib"));
yield* $(symlink("../shared/utils", "src/utils"));

const found = yield* $(exists("tsconfig.json"));
const files = yield* $(glob("src/**/*.ts", "."));
```

#### 进程执行

```typescript
const result = yield* $(exec("git", ["status", "--short"]));
// result: { stdout: string, stderr: string, exitCode: number }

const simple = yield* $(execSimple("ls -la"));
```

#### 用户提示

```typescript
const name = yield* $(promptText("name", "Project name?", "my-app"));
const ok = yield* $(promptConfirm("confirm", "Continue?", true));
const lang = yield* $(promptSelect("lang", "Language?", [
  { label: "TypeScript", value: "ts" },
  { label: "JavaScript", value: "js" },
]));
const features = yield* $(promptMultiselect("features", "Features?", [
  { label: "Linting", value: "lint" },
  { label: "Testing", value: "test" },
]));
```

#### 日志

```typescript
yield* $(debug("Verbose detail"));   // shown with --verbose
yield* $(info("Progress update"));
yield* $(warn("Non-fatal issue"));
yield* $(error("Something broke"));
```

### A. 背景情况

上下文是一个密钥-价值商店,在任务执行期间存在. 使用它来在步骤之间传递数据, 而不需要线程值 :

```typescript
import { gen, $, setContext, getContext, withContext } from "@canonical/task";

const setup = gen(function* () {
  yield* $(setContext("projectName", "my-app"));

  // later, in a different part of the task tree
  const name = yield* $(getContext<string>("projectName"));
  // name: "my-app"
});

// withContext sets a key for the duration of a child task
const scoped = withContext("env", "production", deployTask);
```

上下文辅以 `Map<string, unknown>` 在制作翻译。 默认情况下干运行的解析器不会持续上下文 – 使用 `dryRunWith` 以提供模拟上下文值.

### 错误模型

结构化任务失败 `TaskError` 值 :

```typescript
interface TaskError {
  code: string;              // programmatic error code
  message: string;           // human-readable description
  cause?: unknown;           // original error that caused this failure
  context?: Record<string, unknown>;  // additional structured data
  stack?: string;            // stack trace if available
  suppressed?: TaskError[];  // for parallel: all errors, not just the first
}
```

#### 创建错误

```typescript
import { fail, failWith } from "@canonical/task";

const notFound = failWith("FILE_NOT_FOUND", "Config file missing");

const detailed = fail({
  code: "VALIDATION_FAILED",
  message: "Schema mismatch",
  context: { path: "config.json", expected: "v2" },
});
```

框架定义了基础错误代码( :`FILE_NOT_FOUND`, (中文(简体) ), `EXEC_FAILED`, (中文(简体) ), `PROMPT_CANCELLED`, (中文(简体) ), `TASK_INTERRUPTED`, (中文(简体) ), `INTERNAL`;消费者可以使用任意字符串代码.

#### 处理错误

```typescript
import { recover, mapError, orElse, optional, attempt, fold } from "@canonical/task";

// recover: catch a failure and produce a new task
const safe = recover(riskyTask, (err) =>
  err.code === "FILE_NOT_FOUND" ? writeFile("config.json", "{}") : fail(err),
);

// mapError: transform the error without changing recovery
const retagged = mapError(innerTask, (err) => ({
  ...err,
  code: "DEPLOY_FAILED",
  context: { ...err.context, phase: "build" },
}));

// orElse: try A, fall back to B
const config = orElse(readFile("config.local.json"), readFile("config.json"));

// optional: swallow failure, return undefined
const maybePkg = optional(readFile("package.json"));

// attempt: capture success or failure as a value
const result = yield* $(attempt(riskyTask));
if (result.ok) { /* result.value */ } else { /* result.error */ }

// fold: handle both branches and unify the type
const status = fold(deployTask, () => "deployed", (err) => `failed: ${err.code}`);
```

#### 并行错误

何时 `parallel` 任务失败, 第一个错误变为主错误 `TaskError` 和任何剩余错误作为附件附上 `suppressed`数字 :

```typescript
// If tasks[0] and tasks[2] both fail:
// error.code      → first failure's code
// error.suppressed → [task[2]'s error]
```

### 组合器

将任务编成更大的工作流程:

#### 顺序

```typescript
import { sequence, sequence_, traverse, traverse_ } from "@canonical/task";

// Run in order, collect results
const contents = sequence([readFile("a.txt"), readFile("b.txt")]);

// Run in order, discard results
sequence_([writeFile("a.txt", "A"), writeFile("b.txt", "B")]);

// Map + sequence over an array
const compiled = traverse(sourceFiles, (f) => readFile(f));

// Map + sequence, discard results
traverse_(files, (f) => deleteFile(f));
```

#### 并行执行

```typescript
import { parallel, parallelN, race } from "@canonical/task";

// Run concurrently, collect all results
const all = parallel([fetchA, fetchB, fetchC]);

// Run with concurrency limit (batches of 3)
const throttled = parallelN(3, manyTasks);

// Return first to complete
const fastest = race([mirrorA, mirrorB]);
```

#### 条件

```typescript
import { when, unless, ifElse, whenM, ifElseM } from "@canonical/task";

// Run only if condition is true
when(isDirty, cleanUp);

// Run only if condition is false
unless(isCI, promptForConfirmation);

// Choose between two tasks
ifElse(hasConfig, loadConfig, useDefaults);

// Condition is itself a task
whenM(exists("package.json"), installDeps);

// Both condition and branches are tasks
ifElseM(exists(".env"), loadEnv, createEnv);
```

#### 调度

```typescript
import { switchMap } from "@canonical/task";

// Detect-then-dispatch: run a detection task, branch on its result
const result = switchMap(
  detectHarness,                // Task<"jest" | "vitest" | null>
  {
    jest: configureJest,        // Task<Config>
    vitest: configureVitest,    // Task<Config>
  },
  useDefaultConfig,             // fallback Task<Config>
);
```

#### 处理错误

```typescript
import { retry, orElse, optional, attempt, bracket, ensure } from "@canonical/task";

// Retry up to 3 times
const resilient = retry(flakyTask, 3);

// Try primary, fall back to secondary
const config = orElse(readFile("local.json"), readFile("default.json"));

// Swallow errors, return undefined
const maybe = optional(readFile("optional.json"));

// Capture result or error as a value
const result = attempt(riskyTask);

// Resource management (acquire → use → release, even on failure)
const safe = bracket(acquireConn, useConn, releaseConn);

// Ensure cleanup runs regardless of outcome
const withCleanup = ensure(mainTask, cleanup);
```

#### 公用事业

```typescript
import { tap, tapError, fold, zip, zip3 } from "@canonical/task";

// Side-effect without changing the value
const logged = tap(readFile("a.txt"), (content) => info(`Read ${content.length} bytes`));

// Side-effect on failure
const observed = tapError(riskyTask, (err) => error(`Failed: ${err.code}`));

// Handle both success and failure → unified type
const status = fold(deploy, () => "ok", (err) => `fail: ${err.code}`);

// Combine tasks into tuples
const [a, b] = yield* $(zip(readFile("a.txt"), readFile("b.txt")));
const [x, y, z] = yield* $(zip3(taskX, taskY, taskZ));
```

### 流利构建器

这个 `task()` 编号 : `of()` API 提供了一个可连锁的选项 :

```typescript
import { task, of, mkdir, writeFile, info } from "@canonical/task";

const result = task(mkdir("output"))
  .andThen(writeFile("output/a.txt", "A"))
  .andThen(writeFile("output/b.txt", "B"))
  .andThen(info("Done!"))
  .unwrap();

const doubled = of(21)
  .map((n) => n * 2)
  .flatMap((n) => writeFile("answer.txt", String(n)))
  .unwrap();
```

打电话 `.unwrap()` 以提取基础 `Task<A>` 当你需要传递给梳理员或口译员时.

### 口译员

任务是惰性数据,直到一个解释者走过结构并决定如何处理每个效果. 包裹运送了三名口译员:

#### 制作口译`runTask`(中文(简体) )

执行对真 I/O 的效果——文件系统,进程,提示 :

```typescript
import { runTask } from "@canonical/task/node";

const value = await runTask(myTask);
```

`runTask` 接受一个 `RunTaskOptions` 对象 :

```typescript
await runTask(myTask, {
  // Shared key-value context for ReadContext/WriteContext effects
  context: new Map([["env", "production"]]),

  // Custom prompt handler (required if the task uses Prompt effects)
  promptHandler: async (effect) => {
    // effect.question: PromptQuestion
    return "user input";
  },

  // Log routing (default: console.log with level prefix)
  onLog: (level, message) => logger[level](message),

  // Effect lifecycle hooks
  onEffectStart: (effect) => { /* before each effect */ },
  onEffectComplete: (effect, durationMs) => { /* after each effect */ },

  // AbortSignal for interruption
  signal: controller.signal,
});
```

当任务失败时, `runTask` 丢出一个 `TaskExecutionError` 包起来 `TaskError`数字 :

```typescript
import { TaskExecutionError } from "@canonical/task";
import { runTask } from "@canonical/task/node";

try {
  await runTask(myTask);
} catch (err) {
  if (err instanceof TaskExecutionError) {
    console.log(err.code);          // error code string
    console.log(err.taskError);     // full TaskError object
  }
}
```

#### 干线传译员(`dryRun`(中文(简体) )

收集效果而不执行它们。 每个效果都有一个模拟返回值,这样任务就可以继续:

```typescript
import { dryRun } from "@canonical/task";

const { value, effects } = dryRun(myTask);
// value: the task's return value (using mocked effect results)
// effects: Effect[] — every effect the task would have performed
```

默认模拟 : `ReadFile` → `"[mock content of <path>]"`, (中文(简体) ), `Exists` → `true`, (中文(简体) ), `Exec` → `{ stdout: "", stderr: "", exitCode: 0 }`, (中文(简体) ), `Prompt` 缺省或首选,写效果 `undefined`。 。 。 .

干运行的口译员跟踪虚拟文件系统——由 `WriteFile` 或者说 `MakeDir` 效果可见于后续 `Exists` 在同一跑道内检查.

##### 自定义模拟 `dryRunWith`

覆盖每个效果类型的模拟行为 :

```typescript
import { dryRunWith } from "@canonical/task";

const mocks = new Map([
  ["ReadFile", (effect) => {
    if (effect.path === "package.json") return '{"name": "my-app"}';
    return "default content";
  }],
  ["Exists", () => false],
]);

const { value, effects } = dryRunWith(myTask, mocks);
```

##### 效应分析公用事业

```typescript
import {
  collectEffects, countEffects, filterEffects,
  getFileWrites, getAffectedFiles,
} from "@canonical/task";

const effects = collectEffects(myTask);

countEffects(effects);
// { WriteFile: 3, ReadFile: 1, Log: 2 }

filterEffects(effects, "WriteFile");
// [{ _tag: "WriteFile", path: "...", content: "..." }, ...]

getFileWrites(effects);
// [{ path: "a.txt", content: "A" }, ...]

getAffectedFiles(effects);
// ["a.txt", "b.txt", "output/"] — sorted, deduplicated
```

#### 撤消口译( E)`runUndo`(中文(简体) )

以被嘲弄的前向效果行走任务树( 如 `dryRun`),收集 `undo` 任务,然后按倒转顺序执行。 这样可以 `--undo` 在任何没有存储状态的 CLI 命令上—— 相同的任务定义加上相同的答案产生决定性的撤消.

```typescript
import { collectUndos } from "@canonical/task";
import { runUndo } from "@canonical/task/node";

// Forward run:
await runTask(generator.generate(answers));

// Undo (later, same answers):
await runUndo(generator.generate(answers));
```

##### 如何撤销元数据

可写效果带有可选 `undo?: Task<void>` 字段。 初级设备提供合理的默认:

| 初级 | 默认撤销 |
| --- | --- |
| `writeFile` | `deleteFile(path)` |
| `mkdir` | `deleteDirectory(path)` |
| `copyFile` | `deleteFile(dest)` |
| `copyDirectory` | `deleteDirectory(dest)` |
| `symlink` | `deleteFile(linkPath)` |
| `appendFile` | 无——提供自定义 |
| `deleteFile` | 无——提供自定义 |
| `exec` | 无——提供自定义 |

##### 自定义取消

覆盖默认撤消或提供不存在默认的默认:

```typescript
// Custom undo for append
appendFile(indexPath, exportLine, true, {
  undo: removeLineFromFile(indexPath, exportLine),
});

// Disable default undo
writeFile(tempPath, content, { undo: null });
```

##### 不执行而收集撤消

```typescript
const undos = collectUndos(myTask);
// undos: Task<void>[] — undo tasks in forward execution order
// (runUndo reverses them automatically)
```

##### 可编译性

撤消所有现有的组合符自动作曲 :

-   `when(false, writeFile(...))` - 跳过的效果不会产生倒数
-   `sequence_([...])` ——撤销顺序收集,倒转执行
-   `parallel([...])` - 从所有儿童那里收集的废品
-   `gen(function* () { ... })` - 工作相同

##### 测试断言

```typescript
import { assertEffects, assertFileWrites, expectTask } from "@canonical/task";

// Assert exact effect sequence
assertEffects(myTask, [
  { _tag: "MakeDir", path: "output" },
  { _tag: "WriteFile", path: "output/index.ts" },
]);

// Assert which files would be written
assertFileWrites(myTask, ["output/index.ts", "output/package.json"]);

// Fluent matcher
const result = expectTask(myTask);
result.toHaveValue("done");
result.toHaveEffectCount(3);
result.toWriteFile("output/index.ts");
result.toNotWriteFile("output/secret.key");
```

## API 总结

| 类别 | 密钥导出 |
| --- | --- |
| **修道院** | `pure`, (中文(简体) ), `flatMap`, (中文(简体) ), `map`, (中文(简体) ), `ap`, (中文(简体) ), `fail`, (中文(简体) ), `failWith`, (中文(简体) ), `recover`, (中文(简体) ), `mapError` |
| **发电机** | `gen`, (中文(简体) ), `$`, (中文(简体) ), `TaskGen` |
| **构建器** | `task`, (中文(简体) ), `of`, (中文(简体) ), `TaskBuilder` |
| **警卫** | `isPure`, (中文(简体) ), `isFailed`, (中文(简体) ), `hasEffects` |
| **初级** | `readFile`, (中文(简体) ), `writeFile`, (中文(简体) ), `appendFile`, (中文(简体) ), `copyFile`, (中文(简体) ), `copyDirectory`, (中文(简体) ), `deleteFile`, (中文(简体) ), `deleteDirectory`, (中文(简体) ), `mkdir`, (中文(简体) ), `exists`, (中文(简体) ), `symlink`, (中文(简体) ), `glob`, (中文(简体) ), `sortFileLines` |
| **进程** | `exec`, (中文(简体) ), `execSimple` |
| **提示** | `prompt`, (中文(简体) ), `promptText`, (中文(简体) ), `promptConfirm`, (中文(简体) ), `promptSelect`, (中文(简体) ), `promptMultiselect` |
| **日志** | `log`, (中文(简体) ), `debug`, (中文(简体) ), `info`, (中文(简体) ), `warn`, (中文(简体) ), `error` |
| **A. 背景情况** | `getContext`, (中文(简体) ), `setContext`, (中文(简体) ), `withContext` |
| **纯** | `noop`, (中文(简体) ), `succeed` |
| **顺序** | `sequence`, (中文(简体) ), `sequence_`, (中文(简体) ), `traverse`, (中文(简体) ), `traverse_` |
| **平行** | `parallel`, (中文(简体) ), `parallelN`, (中文(简体) ), `race` |
| **条件** | `when`, (中文(简体) ), `unless`, (中文(简体) ), `ifElse`, (中文(简体) ), `whenM`, (中文(简体) ), `ifElseM` |
| **调度** | `switchMap` |
| **处理错误** | `retry`, (中文(简体) ), `retryWithBackoff`, (中文(简体) ), `orElse`, (中文(简体) ), `optional`, (中文(简体) ), `attempt` |
| **资源** | `bracket`, (中文(简体) ), `ensure` |
| **公用事业** | `tap`, (中文(简体) ), `tapError`, (中文(简体) ), `delay`, (中文(简体) ), `timeout`, (中文(简体) ), `fold`, (中文(简体) ), `zip`, (中文(简体) ), `zip3` |
| **制作翻译** (单位:千美元)`@canonical/task/node`(中文(简体) ) | `runTask`, (中文(简体) ), `run`, (中文(简体) ), `executeEffect`, (中文(简体) ), `RunTaskOptions` |
| **干线翻译** | `dryRun`, (中文(简体) ), `dryRunWith`, (中文(简体) ), `mockEffect`, (中文(简体) ), `collectEffects`, (中文(简体) ), `countEffects`, (中文(简体) ), `filterEffects`, (中文(简体) ), `getFileWrites`, (中文(简体) ), `getAffectedFiles`, (中文(简体) ), `assertEffects`, (中文(简体) ), `assertFileWrites`, (中文(简体) ), `expectTask` |
| **撤销** | `collectUndos` (基数), `UndoOptions` (基数); `runUndo`, (中文(简体) ), `UndoResult` (单位:千美元)`@canonical/task/node`(中文(简体) ) |
| **错误** | `TaskExecutionError` (基地——由口译员扔出,可在任何地方取用) |
| **效果构建器** | `readFileEffect`, (中文(简体) ), `writeFileEffect`, (中文(简体) ), `appendFileEffect`, (中文(简体) ), `copyFileEffect`, (中文(简体) ), `copyDirectoryEffect`, (中文(简体) ), `deleteFileEffect`, (中文(简体) ), `deleteDirectoryEffect`, (中文(简体) ), `makeDirEffect`, (中文(简体) ), `existsEffect`, (中文(简体) ), `symlinkEffect`, (中文(简体) ), `globEffect`, (中文(简体) ), `execEffect`, (中文(简体) ), `promptEffect`, (中文(简体) ), `logEffect`, (中文(简体) ), `readContextEffect`, (中文(简体) ), `writeContextEffect`, (中文(简体) ), `parallelEffect`, (中文(简体) ), `raceEffect` |
| **效果公用事业** | `describeEffect`, (中文(简体) ), `isWriteEffect`, (中文(简体) ), `getAffectedPaths` |

## 许可证

LGPL-3.0 (英语)

## 常见问题

### @canonical/task 是什么？

用于可相容,可测试,可干燥的CLI操作的一元效应框架\.

### 如何安装 @canonical/task？

在项目目录运行 npm install @canonical/task，或使用 pnpm add / yarn add / bun add。

### @canonical/task 支持 TypeScript 吗？

支持。包内自带 TypeScript 类型声明（package\.json 声明了 types 入口），安装后即可获得类型提示，无需再安装 @types 包。

### @canonical/task 支持 ESM 还是 CommonJS？

以 ES Module 形式发布，推荐使用 import 引入。在 CommonJS 项目中，Node\.js 20\.19\+ / 22\.12\+ 可以直接 require\(\) 不含顶层 await 的 ES Module，更早的版本需要改用动态 import\(\)。

### @canonical/task 可以商用吗？

@canonical/task 使用 LGPL-3\.0 许可。弱 Copyleft：作为库被调用时通常不要求开源你自己的代码，但修改库本身需以 LGPL 开源；前端打包合并后的适用边界存在不同理解。以上为概括，不构成法律意见，请以许可原文为准。

### @canonical/task 的下载量有多少？

根据 npm 官方下载统计，@canonical/task 在 2026/10/09 – 2026/11/07 这 30 天内被下载 2,662 次，日均约 89 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入，适合横向比较热度，不能直接等同于使用人数。

### @canonical/task 还在维护吗？

最新版本 0\.44\.0 发布于 2026/10/23（17 天前）；至今共发布 27 个版本，其中过去 12 个月发布了 27 个；首次发布于 2026/03/26。发布频率只是参考信号：功能稳定的工具可能很少需要更新，建议结合源码仓库的 issue 和提交记录判断。

