Claude Code Plugins

Community-maintained marketplace

Feedback

Essential Deno TypeScript practices for ALL Deno development: configuration, imports, testing, permissions, and anti-patterns. Read this skill for any Deno project setup, dependency management, or core development work.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name deno-core
description Essential Deno TypeScript practices for ALL Deno development: configuration, imports, testing, permissions, and anti-patterns. Read this skill for any Deno project setup, dependency management, or core development work.

Deno Core Best Practices

When to Use This Skill

Use this skill for ALL Deno TypeScript development:

  • Setting up new Deno projects
  • Writing Deno applications or libraries
  • Configuring build, test, and deployment
  • Working with dependencies and imports

Core Deno Philosophy

One Tool, Zero Dependencies

  • Deno is the only tool you need for TypeScript development
  • Built-in tooling: typecheck, lint, format, test, coverage, benchmark
  • Avoid node_modules at all costs - reduce supply chain attack surface
  • No need for: tsc, eslint, prettier, jest, vitest, webpack, etc.

TypeScript Excellence

  • Strict TypeScript adherence - not just "TS support"
  • Bleeding-edge TypeScript features by default - no flags, no config needed
  • No compilation step - just run your code
  • Target ES2024+ with Stage 3 TC39 proposals

Security First

  • Explicit permissions model (no implicit file system or network access)
  • Supply chain security through minimal external dependencies
  • First-class support for modern security patterns

Language & Compiler

TypeScript Configuration

  • Do not use tsconfig.json - Deno uses deno.json(c) as the single source of truth
  • Type-checking powered by deno check / deno test - do not rely on external tsc
  • Default module format is ESM only - no CommonJS interop
  • Prefer Deno's runtime-provided types (Deno.*, Web APIs, Fetch, URLPattern) over polyfills

Strictest Compiler Settings

Always use the strictest possible settings in deno.json:

{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "noImplicitReturns": true,
    "noImplicitThis": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "exactOptionalPropertyTypes": true
  }
}

Configuration & Tasks

deno.json - Single Source of Truth

Use deno.json or deno.jsonc as the single configuration file for:

  • Compiler options
  • Linting and formatting rules
  • Tasks (script aliases)
  • Import maps (dependency management)
  • Exclusions

Complete Configuration Example:

{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "noImplicitReturns": true,
    "noImplicitThis": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true
  },
  "tasks": {
    "dev": "deno run --watch --allow-net --allow-read --allow-env src/main.ts",
    "test": "deno test --allow-net --allow-read --allow-env --coverage=coverage src/",
    "test:unit": "deno test --allow-net --allow-read --allow-env --coverage=coverage src/",
    "test:e2e": "deno test --allow-net --allow-read --allow-env tests/e2e/",
    "test:watch": "deno test --allow-net --allow-read --allow-env --watch --fail-fast",
    "coverage": "deno coverage coverage --html",
    "check": "deno check $(find src -name '*.ts' -not -name '*.sql')",
    "lint": "deno lint",
    "fmt": "deno fmt"
  },
  "imports": {
    "@/": "./src/",
    "@/domain/": "./src/domain/",
    "@/infrastructure/": "./src/infrastructure/",
    "@/application/": "./src/application/",
    "@std/assert": "jsr:@std/assert@^1.0.14",
    "@std/fs": "jsr:@std/fs@^1.0.19",
    "@std/testing": "jsr:@std/testing@^1.0.15",
    "@std/ulid": "jsr:@std/ulid@1",
    "zod": "npm:zod@^3.23.8"
  },
  "exclude": [
    "coverage/",
    "node_modules/"
  ],
  "lock": true
}

Essential Tasks

Define these deno task aliases at minimum:

  • dev - Development with watch mode
  • test, test:watch - Testing
  • coverage - Generate coverage reports
  • check - Type-check all files
  • lint, fmt - Code quality

Lockfile Management

  • Always commit deno.lock to version control
  • Run with --lock=deno.lock --lock-write=false in CI
  • Update lockfile: deno cache --lock=deno.lock --lock-write

Imports & Module Resolution

Import Strategy

CRITICAL: Never use direct JSR/npm imports in source files. All external dependencies MUST be declared in deno.json import map.

Import Order in Source Files:

// 1. Standard library imports (via import map)
import { assertEquals } from "@std/assert";

// 2. Third-party imports (via import map)
import { z } from "zod";

// 3. Internal imports (absolute paths using import map)
import { Agent } from "@/domain/agent.ts";

// 4. Relative imports (only within same module/context)
import { validatePrompt } from "./validation.ts";

Dependency Source Priority

Use sources in this order:

  1. jsr: registry (first choice for TypeScript modules)

    "@std/assert": "jsr:@std/assert@^1.0.14"
    
  2. npm: specifier (when needed; prefer ESM-compatible)

    "zod": "npm:zod@^3.23.8"
    
  3. URL imports (rarely needed with import maps)

Version Pinning

CRITICAL: Version pin all external imports. No floating @latest in committed code.

{
  "imports": {
    "zod": "npm:zod@^3.23.8",           // GOOD - pinned
    "zod": "npm:zod",                    // BAD - no version
    "@std/assert": "jsr:@std/assert@1"   // GOOD - pinned
  }
}

Internal Path Aliases

Use import map aliases for clean internal imports:

{
  "imports": {
    "@/": "./src/",
    "@/domain/": "./src/domain/",
    "@/infrastructure/": "./src/infrastructure/"
  }
}
// GOOD - Clean, refactor-safe
import { Agent } from "@/domain/agent.ts";

// BAD - Brittle relative paths
import { Agent } from "../../../domain/agent.ts";

Type-Only Imports

Use type-only imports when importing types:

import type { Agent } from "@/domain/agent.ts";
import type { z } from "zod";

Testing

Test Organization

Unit Tests - Co-located with Source:

src/
└── domain/
    ├── agent.ts
    └── agent.test.ts          # Unit tests next to code

Integration & E2E Tests - Separate Directory:

tests/
├── integration/
│   └── openai_provider.test.ts
└── e2e/
    └── workflow.test.ts

Why Co-location:

  • Discoverability - tests next to code
  • Maintenance - easy to keep in sync
  • Deno convention - follows deno test discovery

Coverage Requirements

Non-Negotiable:

  • Line coverage: 80%+ - MUST be met
  • Branch coverage: 60-80% - MUST be met
deno test --coverage=coverage
deno coverage coverage --html

Test Structure

Always use explicit AAA (Arrange-Act-Assert):

import { assertEquals } from "@std/assert";

Deno.test("agent should process valid input", () => {
  // Arrange
  const agent = new Agent({ name: "TestAgent" });
  const input = "Hello, world!";

  // Act
  const result = agent.process(input);

  // Assert
  assertEquals(result.status, "success");
});

Test Development

Red-Green-Refactor with fast feedback:

# Watch mode with fail-fast
deno test --watch --fail-fast

# Run specific file
deno test src/domain/agent.test.ts --watch

Deterministic Tests

CRITICAL: All tests must be deterministic.

Test Flakiness Policy:

  • Flakiness = highest priority bug
  • Never ignore, retry, or "fix" with delays
  • Action: investigate, quarantine, fix
  • Do NOT merge flaky tests

Use stable seeds and fixtures:

import { FakeTime } from "@std/testing/time";

Deno.test("timer test", () => {
  using time = new FakeTime();
  // Deterministic time control
  time.tick(1000);
});

Test File Naming

All test files must end with .test.ts:

agent.test.ts     # GOOD
agent_test.ts     # BAD
agent.spec.ts     # BAD

Testing Tools

  • Use @std/assert for assertions
  • Use @std/testing/mock for test doubles
  • Use @std/testing/time for time control
  • Do NOT use Jest - use Deno's built-in runner

Permissions & Security

Principle of Least Privilege

Default to minimum required permissions:

# BAD
deno run --allow-all script.ts

# GOOD
deno run --allow-read=./data --allow-net=api.example.com script.ts

Common Permission Flags

--allow-read[=<path>]      # File system read
--allow-write[=<path>]     # File system write
--allow-net[=<domain>]     # Network access
--allow-env[=<var>]        # Environment variables
--allow-run[=<program>]    # Subprocess execution

Document Required Permissions

/**
 * Fetches data from API and caches locally.
 *
 * Required permissions:
 * - --allow-net=api.example.com
 * - --allow-read=./cache
 * - --allow-write=./cache
 */
export async function fetchData(): Promise<Data> {
  // ...
}

Secrets Management

// BAD - Hardcoded
const apiKey = "sk-1234";

// GOOD - From environment
const apiKey = Deno.env.get("API_KEY");
if (!apiKey) {
  throw new Error("API_KEY required");
}

Run with: deno run --allow-env=API_KEY script.ts


Anti-Patterns to Avoid

Import Anti-Patterns

// BAD - Direct JSR/npm imports in source
import { z } from "npm:zod@^3.23.8";

// GOOD - Use import map
import { z } from "zod";
// BAD - Floating versions
"zod": "npm:zod"

// GOOD - Pinned versions
"zod": "npm:zod@^3.23.8"

Node.js Anti-Patterns

// BAD - Node.js APIs
const fs = require("fs");
import * as fs from "node:fs";

// GOOD - Deno APIs
await Deno.readTextFile("file.txt");

Testing Anti-Patterns

// BAD - Unnecessary delay
await new Promise(r => setTimeout(r, 100));

// GOOD - Deterministic time
import { FakeTime } from "@std/testing/time";
using time = new FakeTime();
time.tick(100);

Permission Anti-Patterns

# BAD - Overly broad
deno run --allow-all script.ts

# GOOD - Specific
deno run --allow-read=./data script.ts

Async Anti-Patterns

// BAD - Unnecessary async
async function validate(input: string): Promise<boolean> {
  return input.length > 0;
}

// GOOD - Remove async if no await
function validate(input: string): boolean {
  return input.length > 0;
}

Quick Command Reference

Development

# Run with watch
deno run --watch src/main.ts

# Type-check
deno check src/**/*.ts

# Format
deno fmt

# Lint
deno lint

Testing

# Run all tests
deno test

# With coverage
deno test --coverage=coverage
deno coverage coverage --html

# Watch mode
deno test --watch --fail-fast

Dependencies

# Update dependencies
deno cache --reload

# Update lockfile
deno cache --lock=deno.lock --lock-write

Tasks

# Run tasks from deno.json
deno task dev
deno task test
deno task coverage

Key Principles Summary

  1. One tool - Deno replaces tsc, eslint, prettier, jest
  2. Security first - Explicit permissions, minimal dependencies
  3. Import maps - All deps in deno.json, never direct imports
  4. Version pinning - No floating versions
  5. Co-located tests - Unit tests next to source
  6. 80%/60% coverage - Line/branch, non-negotiable
  7. No flakiness - Highest priority, never ignore
  8. AAA pattern - Explicit in every test
  9. Least privilege - Minimal permissions
  10. ESM only - No CommonJS

Additional Resources