Skip to content

Static Analysis

TL;DR

Use awaitly-analyze to extract workflow structure from TypeScript, then render Mermaid/JSON/HTML artifacts for docs, review, and CI checks without running workflows.

When To Use

  • You want architecture diagrams from source code, not runtime traces.
  • You need deterministic workflow docs in CI.
  • You want workflow diffs in PR review.
  • You need rule-style diagnostics with stable slug codes.

Static Analysis Pipeline

1

Analyze Source

Parse workflow file and extract IR tree, stats, and references.

2

Render Artifact

Generate Mermaid, JSON, markdown report, or interactive HTML.

3

Apply In Workflow

Use output in docs, PRs, CI gates, and architecture reviews.

Terminal window
# Mermaid markdown (default)
npx awaitly-analyze ./src/workflows/checkout.ts
# JSON
npx awaitly-analyze ./src/workflows/checkout.ts --format=json
# Interactive HTML artifact
npx awaitly-analyze ./src/workflows/checkout.ts --html
# Workflow diff
npx awaitly-analyze --diff v1.ts v2.ts
# Strict diagnostics
npx awaitly-analyze ./src/workflows/checkout.ts --doctor
  • Step IDs from step('id', fn, opts) as canonical names.
  • Workflow and step docs from description and markdown.
  • JSDoc descriptions as jsdocDescription fallback.
  • Optional step metadata: intent, domain, owner, tags, stateChanges, emits, calls, errorMeta.
FlagDefaultDescription
--format=<fmt>mermaidmermaid, json, markdown
--direction=<dir>TBTB, TD, LR, BT, RL
--railwayoffLinear happy-path + ok/err branching
--keysoffShow step cache keys
--errors / --no-errorsonShow/hide error nodes
--htmloffGenerate interactive HTML artifact
--html-output=<path>autoWrite HTML to custom path
FlagDefaultDescription
--types / --no-typesonGenerate .types.ts
--test / --no-testoffGenerate test stubs
--test-runner=<runner>vitestvitest, jest, mocha
-o, --output-adjacentoffWrite output next to source
--suffix=<value>workflowAdjacent output file suffix
--no-stdoutoffSuppress stdout output
--dsl-output=<value>offWrite DSL output
--write-dsloffSame as --dsl-output=.awaitly
--watchoffRe-analyze on source changes
Terminal window
# Writes <basename>.html next to workflow source
npx awaitly-analyze ./src/workflows/checkout.ts --html
# Custom path
npx awaitly-analyze ./src/workflows/checkout.ts --html --html-output=./docs/checkout-diagram.html

The HTML output includes:

  • Mermaid rendered client-side.
  • Click-to-inspect node details.
  • Built-in theme picker with persistence.
  • Self-contained payload for easy sharing.
import {
analyze,
renderStaticMermaid,
renderStaticJSON,
extractNodeMetadata,
generateInteractiveHTML,
} from 'awaitly-analyze';
const ir = analyze('./src/workflows/checkout.ts').single();
const mermaid = renderStaticMermaid(ir, { direction: 'TB', showKeys: false });
const json = renderStaticJSON(ir, { pretty: true });
const metadata = extractNodeMetadata(ir);
const html = generateInteractiveHTML(mermaid, metadata, {
title: 'Checkout Workflow',
direction: 'TB',
});
MethodReturnsThrowsBest for
.single()Single IRIf 0 or >1 workflowsSingle-workflow file
.singleOrNull()IR or nullNeverOptional single workflow
.all()IR arrayNeverMulti-workflow iteration
.named(name)Single IRIf not foundNamed workflow selection
.first()Single IRIf emptyFirst workflow only
.firstOrNull()IR or nullNeverSafe first workflow
Terminal window
# Local files
npx awaitly-analyze --diff before.ts after.ts
# HEAD vs working copy
npx awaitly-analyze --diff src/workflows/checkout.ts
# Git ref vs local
npx awaitly-analyze --diff main:src/workflows/checkout.ts src/workflows/checkout.ts
# GitHub PR
npx awaitly-analyze --diff gh:#123
import {
analyze,
diffWorkflows,
renderDiffMarkdown,
renderDiffJSON,
renderDiffMermaid,
} from 'awaitly-analyze';
const before = analyze('./v1.ts').single();
const after = analyze('./v2.ts').single();
const diff = diffWorkflows(before, after, {
detectRenames: true,
regressionMode: false,
});
const md = renderDiffMarkdown(diff, { showUnchanged: true });
const json = renderDiffJSON(diff);
const mermaid = renderDiffMermaid(after, diff, { showRemovedSteps: true, direction: 'TB' });
OptionDefaultDescription
detectRenamestrueMatch same callee+position as rename
regressionModefalseMark removals as regressions

Use strict slug-keyed diagnostics aligned with runtime and ESLint rule naming.

Terminal window
awaitly-analyze ./src/workflows/checkout.ts --doctor
awaitly-analyze ./src/workflows/checkout.ts --doctor --format=json

Sample output:

✗ [step-require-id]:12:4
Step "<missing>" uses legacy signature without explicit ID
Docs: https://jagreehal.github.io/awaitly/rules/#step-require-id
Fix: Use step('id', fn, opts) instead of step(fn, opts)
interface StaticWorkflowIR {
root: StaticWorkflowNode;
metadata: StaticAnalysisMetadata;
references: Map<string, StaticWorkflowIR>;
}
interface AnalysisStats {
totalSteps: number;
conditionalCount: number;
parallelCount: number;
raceCount: number;
loopCount: number;
workflowRefCount: number;
unknownCount: number;
}

Node categories:

  • StaticStepNode
  • StaticSequenceNode
  • StaticParallelNode
  • StaticRaceNode
  • StaticConditionalNode
  • StaticLoopNode
  • StaticWorkflowRefNode
  • StaticUnknownNode
FeatureDetection
Stepsstep(), step.retry(), step.withTimeout()
Conditionalsif/else, when*, unless*
Loopsfor, while, for-of, for-in
Parallelstep.all(), allAsync(), allSettledAsync()
Racestep.race(), anyAsync()
Workflow refsChild workflow run calls

Which Output Should You Use?

Choice Best for Tradeoff
--format=mermaid Readable architecture diagrams in docs and PRs. Less detail than full JSON payload.
--format=json CI, automation, and custom tooling. Harder for humans to skim quickly.
--html Shareable deep review with click-to-inspect details. Heavier artifact than markdown output.
--diff PR-level workflow change review. Requires clean baseline/version selection.
--doctor Strict diagnostics and policy enforcement. Can surface warnings that require migration work.
  • Dynamic step IDs can appear as <dynamic>.
  • External workflow references are only resolved when analyzable in context.