@fullystudios/zed-npm-script-tasks
v0.1.1
Published
Generate a Zed .zed/tasks.json from every package.json#scripts in a repo, including monorepo workspace packages
Readme
@fullystudios/zed-npm-script-tasks
Generate a Zed .zed/tasks.json from every
package.json#scripts in a repo, including monorepo workspace packages, so all of
them are runnable from the cmd-shift-r task modal.
Why
Zed already surfaces package.json scripts, but only when a JS/TS/TSX or
package.json buffer is the active editor, and it only walks upward from that
file. Focus a README.md or a .toml, or no file at all, and they all disappear.
Zed also never scans downward into workspace packages.
This writes the scripts to disk instead, so they are always in the modal regardless of what is focused, and monorepo packages are included.
This is a CLI, not a Zed extension. Zed's extension API has no filesystem write capability and no task-provider hook, so an extension cannot do this.
Install
Per project
npm install --save-dev @fullystudios/zed-npm-script-tasksAdd a script:
{
"scripts": {
"zed:sync-tasks": "npx zed-npm-script-tasks sync"
}
}npm run zed:sync-tasks.zed/tasks.json is meant to be committed. Add npm run zed:sync-tasks -- --check
to CI so a stale file fails the build.
Globally
Install once and run it in any repo, without adding a dependency or a script to that repo:
npm install --global @fullystudios/zed-npm-script-taskscd /path/to/repo
zed-npm-script-tasks syncOr from anywhere, with --cwd:
zed-npm-script-tasks sync --cwd /path/to/repoEither way the tool finds the repo root itself and writes <root>/.zed/tasks.json,
so it does not matter which subdirectory you are standing in. The repo needs no
node_modules at all: the version of the tool that runs is the global one, and the
package manager it puts in the generated command is still read from the repo (its
packageManager field or lockfile).
Useful for repos that are not yours to modify, or when you would rather not have a
Zed-specific devDependency in every project. Keep the per-project install as well if
you want --check in CI, since a globally installed, unpinned version is not
something CI can reproduce.
Managing the global install:
npm install --global @fullystudios/zed-npm-script-tasks@latest # upgrade
npm list --global --depth 0 | grep zed-npm-script-tasks # which version
npm uninstall --global @fullystudios/zed-npm-script-tasks # removepnpm (pnpm add --global), Yarn (yarn global add) and Bun (bun add --global)
work too, as does running it without installing anything:
npx @fullystudios/zed-npm-script-tasks syncUsage
zed-npm-script-tasks sync [options] write .zed/tasks.json
zed-npm-script-tasks watch [options] sync now, then re-sync when manifests change
-c, --check exit 1 if .zed/tasks.json is out of date; never writes (CI)
--dry-run print what would be written to stdout; never writes
--cwd <dir> start directory for root detection (default: process.cwd())
-q, --quiet suppress the summary line
-h, --help show this help
-v, --version show versionWhat it generates
One task per script, labelled <package name>: <script>:
[
// >>> zed-npm-script-tasks:begin (generated - do not edit inside this block; run `zed-npm-script-tasks sync`)
{
"label": "site: dev",
"command": "pnpm",
"args": ["run", "dev"],
"cwd": "$ZED_WORKTREE_ROOT/src/apps/site"
},
// <<< zed-npm-script-tasks:end
]Labels are package-qualified because a name like check-types typically exists in
many manifests at once. cwd uses Zed's $ZED_WORKTREE_ROOT variable, never an
absolute path, so the committed file works on every machine.
Your own tasks are safe
Anything outside the sentinel comments is preserved byte-for-byte, including your comments and formatting. The file is never parsed and reserialized, only spliced.
- No file yet: one is created.
- File with a managed block: only the block's interior is replaced.
- File without a managed block: a block is inserted above your existing tasks.
- File that is not a JSON array, or whose hand-written part is already malformed: the tool refuses to write and tells you. This matters because Zed discards the entire file on a single parse error.
Writes are atomic (temp file plus rename) and skipped entirely when the result would
be identical, so git status stays clean and Zed's file watcher is not churned.
Discovery
- Root is the nearest ancestor with a
package.json, preferring the git root, to match the directory you open in Zed. - Workspace packages come from
pnpm-workspace.yamlpackages, elsepackage.jsonworkspaces(array or{ "packages": [...] }). Negated globs like!docsare honoured. - Only directories that actually contain a
package.jsoncount, so a glob likescripts/*will not pick up loose files, and build output such as.next/package.jsonis excluded. - Lifecycle hooks are skipped when their base script exists in the same manifest:
postsyncis dropped becausesyncexists, whilepreviewandprepublishOnlyare kept. - Package manager is the nearest
packageManagerfield, else inferred from the root lockfile, elsenpm. Only the name is used, so Corepack or Volta still picks the version. - Packages with no scripts contribute nothing. A malformed manifest is warned about and skipped rather than failing the run.
Ordering is deterministic (root first, then packages by path; scripts in declaration order) so re-running produces a byte-identical file and readable diffs.
Duplicate entries in the modal
Zed's own providers also list package.json scripts when a JS/TS or package.json
buffer is focused, so you may see a script up to three times (site: dev,
run dev, package.json > dev). To show only these generated tasks, add to
.zed/settings.json:
{
"languages": {
"JSON": { "tasks": { "enabled": false } },
"TypeScript": { "tasks": { "enabled": false } }
}
}Caveats
- Once any
.zed/tasks.jsonexists in a worktree, Zed ignores all tasks in that worktree's.vscode/tasks.json. The tool warns when it first creates the file in a repo that has one. - Zed only lists a worktree's tasks when that worktree is the active one. Scripts from other folders in a multi-folder window will not appear.
- Requires Node 22 or newer (
fs.globSync).
Development
npm install
npm test # node:test
npm run typecheck # tsc --noEmit over JSDoc typesTry it against another repo without installing it there:
node bin/cli.mjs sync --cwd /path/to/repo --dry-runLicense
MIT
