char/avril

(wip) why'd you have to go and make things so complicaaaaated?

git clone https://git.t4t.associates/char/avril

Charlotte Somextract avril-task crate + add avril:task module7f4058a

main
5.4 KiB105 linesraw

avril.conf

avril.conf is written in "friendly JSON", with an implicit top-level object, // comments, and trailing + implicit commas.

name: "@app/demo",
version: "0.1.0", // optional

// dependencies on avril projects
deps: [
  "jsr:@me/my-library@^1.0.0", // depend via jsr
  "https://esm.example/my-library/1.0.0/", // depend via https
  "./lib/my-cool-library", // depend on relative projects
  // avoid name conflicts via explicit `name`:
  { from "../other/my-cool-library", name: "other/my-cool-library" },
],

// miscellaneous import map augmentations
imports: {
  helper: './my-helper.ts'
}

Unknown fields are ignored, including in task and dependency objects, so other tools can store their own configuration alongside avril's. Recognized fields are still validated, and avril add preserves unrelated fields.

packages

  • package metadata strings: name, version, description
  • exports: controls how importing from this project works
    • can be a plain string (e.g. exports: "mod.ts") for a single . export
    • otherwise, can be a map of subpaths to files
  • deps: a list of package dependencies
  • imports: map of bare specifiers to real specifiers (a la <script type="importmap">)
  • scopes: it's like imports but you can scope it to
    • ex.: scopes: { './tests/': { assert: './test-assert.ts' } } maps import assert from "assert" only inside the tests directory
  • compilerOptions: package compiler-option defaults — the JSX options (jsx, jsxImportSource, jsxFactory, jsxFragmentFactory) drive the runtime transform, and every key steers type checking; the nearest tsconfig.json overrides these defaults per module

tasks

tasks maps names to commands to run with avril task, a la deno task:

tasks: {
  build: "avril run build.ts",
  serve: {
    description: "Build, then run the server",
    command: "avril run server.ts",
    dependencies: ["build"],
  },
  all: { dependencies: ["build", "serve"] },
}
  • tasks run with the cwd to the package root (avril.conf's parent dir)
  • task commands run via the system shell (sh -c or cmd /C on Windows) when provided as a string
  • task commands run as direct invocations when provided as an array of strings
  • dependencies run sequentially in listed order. a failure will halt execution
  • a task with parallel: true runs its dependencies all at once instead; if any of them fail, the task fails with status 1 (the individual exit codes are not meaningful once failures race)
  • a task can set envFile (e.g. envFile: ".env") to control environment defaults for itself and all its (recursive) dependencies
    • this affects
  • a task with envFile (e.g. build: {command: "...", envFile: ".env"}) loads a dotenv file, relative to the package root, as environment defaults for itself & recursively for all of its dependencies
    • a dependency's own envFile overrides values it inherited; the process environment overrides every file (same syntax & precedence as --env-file)
    • for a cached task, env-file values only enter its cache key for names listed in its env

watching tasks

avril task --watch <name> runs a task, then reruns it whenever its inputs change:

  • the task, or at least one of its (transitive) dependencies, must declare files; without any input globs there is nothing to watch, and --watch is an error
  • changes are debounced over a short quiet period (50ms), so a burst of saves produces one rerun; a change whose size and modification time match the last run does not rerun anything, so a touch reruns while rewriting a file with identical size and mtime is missed
  • avril.conf and any envFile in the task graph are watched too: editing either reloads it and reruns the task, even if the change is outside the task's cache key
  • if the task is still running when its inputs change, it is restarted: its process group is sent SIGINT, then SIGTERM after a grace period, then SIGKILL (on Windows the task's job is terminated instead)
  • a task that fails does not end watch mode; it reruns on the next change. ctrl-c stops the watcher, terminating any running task the same way

task skipping / output caching

a task can opt into incrementality by declaring its input files / env vars & output files. useful for e.g. an ad-hoc incremental static site generator:

tasks: {
  "build:pages": {
    command: "avril run site/render-pages.ts",
    files: ["content/**", "templates/"],
    output: ["dist/**/*.html"],
  },
  "build:assets": {
    command: "avril run site/bundle-assets.ts",
    files: ["assets/**"],
    output: ["dist/assets/"],
  },
  "build:feed": {
    command: "avril run site/render-feed.ts",
    files: ["content/posts/**", "site.json"],
    output: ["dist/feed.xml", "dist/sitemap.xml"],
    env: ["SITE_URL"],
  },
  build: { dependencies: ["build:pages", "build:assets", "build:feed"], parallel: true },
}
  • files: input glob patterns, relative to the package root - if any file has changed, the task can't be skipped
    • a files pattern matching a directory covers everything underneath (recursively)
    • a pattern that matches no files is an error
  • env: input environment variable names - if any var has changed, the task can't be skipped
  • output: output files - only their sizes and modification times are kept in the task cache. a task can only be skipped while its outputs are still present & unchanged; missing or changed outputs will rerun it
    • you may omit output, in which case skipping is based on the inputs alone
1# avril.conf
2
3`avril.conf` is written in "friendly JSON", with an implicit top-level object, `//` comments, and trailing + implicit commas.
4
5```
6name: "@app/demo",
7version: "0.1.0", // optional
8
9// dependencies on avril projects
10deps: [
11  "jsr:@me/my-library@^1.0.0", // depend via jsr
12  "https://esm.example/my-library/1.0.0/", // depend via https
13  "./lib/my-cool-library", // depend on relative projects
14  // avoid name conflicts via explicit `name`:
15  { from "../other/my-cool-library", name: "other/my-cool-library" },
16],
17
18// miscellaneous import map augmentations
19imports: {
20  helper: './my-helper.ts'
21}
22```
23
24Unknown fields are ignored, including in task and dependency objects, so other tools can store their own configuration alongside avril's. Recognized fields are still validated, and `avril add` preserves unrelated fields.
25
26## packages
27
28- package metadata strings: `name`, `version`, `description`
29- `exports`: controls how importing from this project works
30  - can be a plain string (e.g. `exports: "mod.ts"`) for a single . export
31  - otherwise, can be a map of subpaths to files
32- `deps`: a list of package dependencies
33- `imports`: map of bare specifiers to real specifiers (a la `<script type="importmap">`)
34- `scopes`: it's like `imports` but you can scope it to
35  - ex.: `scopes: { './tests/': { assert: './test-assert.ts' } }` maps `import assert from "assert"` only inside the `tests` directory
36- `compilerOptions`: package compiler-option defaults — the [JSX options](jsx.md) (`jsx`, `jsxImportSource`, `jsxFactory`, `jsxFragmentFactory`) drive the runtime transform, and every key steers type checking; the nearest `tsconfig.json` overrides these defaults per module
37## tasks
38
39`tasks` maps names to commands to run with `avril task`, a la `deno task`:
40
41```
42tasks: {
43  build: "avril run build.ts",
44  serve: {
45    description: "Build, then run the server",
46    command: "avril run server.ts",
47    dependencies: ["build"],
48  },
49  all: { dependencies: ["build", "serve"] },
50}
51```
52
53- tasks run with the cwd to the package root (`avril.conf`'s parent dir)
54- task commands run via the system shell (`sh -c` or `cmd /C` on Windows) when provided as a string
55- task commands run as direct invocations when provided as an array of strings
56- dependencies run sequentially in listed order. a failure will halt execution
57- a task with `parallel: true` runs its dependencies all at once instead; if any of them fail, the task fails with status 1 (the individual exit codes are not meaningful once failures race)
58- a task can set `envFile` (e.g. `envFile: ".env"`) to control environment defaults for itself and all its (recursive) dependencies
59  - this affects
60- a task with `envFile` (e.g. `build: {command: "...", envFile: ".env"}`) loads a dotenv file, relative to the package root, as environment defaults for itself & recursively for all of its dependencies
61  - a dependency's own `envFile` overrides values it inherited; the process environment overrides every file (same syntax & precedence as `--env-file`)
62  - for a cached task, env-file values only enter its cache key for names listed in its `env`
63
64### watching tasks
65
66`avril task --watch <name>` runs a task, then reruns it whenever its inputs change:
67
68- the task, or at least one of its (transitive) dependencies, must declare `files`; without any input globs there is nothing to watch, and `--watch` is an error
69- changes are debounced over a short quiet period (50ms), so a burst of saves produces one rerun; a change whose size and modification time match the last run does not rerun anything, so a touch reruns while rewriting a file with identical size and mtime is missed
70- `avril.conf` and any `envFile` in the task graph are watched too: editing either reloads it and reruns the task, even if the change is outside the task's cache key
71- if the task is still running when its inputs change, it is restarted: its process group is sent SIGINT, then SIGTERM after a grace period, then SIGKILL (on Windows the task's job is terminated instead)
72- a task that fails does not end watch mode; it reruns on the next change. ctrl-c stops the watcher, terminating any running task the same way
73
74### task skipping / output caching
75
76a task can opt into incrementality by declaring its input files / env vars & output files. useful for e.g. an ad-hoc incremental static site generator:
77
78```
79tasks: {
80  "build:pages": {
81    command: "avril run site/render-pages.ts",
82    files: ["content/**", "templates/"],
83    output: ["dist/**/*.html"],
84  },
85  "build:assets": {
86    command: "avril run site/bundle-assets.ts",
87    files: ["assets/**"],
88    output: ["dist/assets/"],
89  },
90  "build:feed": {
91    command: "avril run site/render-feed.ts",
92    files: ["content/posts/**", "site.json"],
93    output: ["dist/feed.xml", "dist/sitemap.xml"],
94    env: ["SITE_URL"],
95  },
96  build: { dependencies: ["build:pages", "build:assets", "build:feed"], parallel: true },
97}
98```
99
100- `files`: input glob patterns, relative to the package root - if any file has changed, the task can't be skipped
101  - a `files` pattern matching a directory covers everything underneath (recursively)
102  - a pattern that matches no files is an error
103- `env`: input environment variable names - if any var has changed, the task can't be skipped
104- `output`: output files - only their sizes and modification times are kept in the task cache. a task can only be skipped while its outputs are still present & unchanged; missing or changed outputs will rerun it
105  - you may omit `output`, in which case skipping is based on the inputs alone