npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

github-projects-md-sync

v0.1.11

Published

A tool to sync GitHub Project V2 items with local markdown files

Readme

GitHub Projects Markdown Sync

npm version MD Sync Series License: MIT

Sync GitHub Projects V2 with Markdown stories. Licensed under the MIT License.

Markdown Example

Overview

The latest release introduces a safer, clearer sync model:

  • Single entry for md→project with create-only enforcement
  • Separated formats: Multi-Story for import, Single-Story for export
  • Dry-run diagnostics for CI and previewing plans

This tool synchronises Markdown documents and GitHub Projects (V2) so teams can manage work in text while keeping the project board current.

Requirements

  • Node.js 18 or newer

Features

  • Create-only import: Multi-Story Markdown → GitHub Project items by Story ID
  • Read-only export: GitHub Project → Single-Story Markdown files
  • Status mapping: Backlog, Ready, In progress, In review, Done (with aliases)
  • Deterministic, idempotent behaviour keyed by Story ID
  • Dry-run with structured logs for CI gates
  • TypeScript API and runnable examples

Quick start

  1. Install the package in a Node.js workspace:
npm install github-projects-md-sync
  1. Create a .env file in the project root with credentials that can access GitHub Projects V2:
GITHUB_TOKEN=your_github_token
PROJECT_ID=your_project_id
  1. Run the CLI commands or consume the TypeScript API as described below.

Usage

CLI

| Command | Purpose | Key options | | --- | --- | --- | | npm run md -- <path> | Import Multi-Story Markdown into a project (create-only) | --dry-run to print the plan without calling the API | | npm run project [-- <Story-ID>] [<outputDir>] | Export all stories or a single story into Markdown files | Positional Story-ID selects a single story, positional outputDir overrides the destination | | npm run project:story -- [Story-ID] [outputDir] | Convenience wrapper for single-story export | Accepts Story-ID and outputDir as positional args or via --story, --output | | npx ts-node src/project-to-stories.ts [Story-ID] [outputDir] | Low-level script that powers the exports | Requires PROJECT_ID and GITHUB_TOKEN env vars; positional arguments follow the same rules |

  • Import Multi-Story Markdown to a GitHub Project (create-only):
npm run md -- stories/test-multi-stories-0.1.11.md
  • Optional dry-run plan: simulates the sync and prints the intended GitHub mutations without executing API writes:
npm run md -- stories/test-multi-stories-0.1.11.md --dry-run
  • Export GitHub Project items to Single-Story Markdown files:
npm run project
npm run project -- <Story-ID>

As a Library

import {
  mdToProject,
  projectToMdWithOptions,
  projectToMdSingleStory
} from "github-projects-md-sync";

const projectId = process.env.PROJECT_ID!;
const githubToken = process.env.GITHUB_TOKEN!;

const mdResult = await mdToProject(projectId, githubToken, "./markdown-files");
const exportAllResult = await projectToMdWithOptions({
  projectId,
  githubToken,
  outputPath: "./output-dir",
  logLevel: "info"
});
const exportSingleResult = await projectToMdSingleStory(
  projectId,
  githubToken,
  "Story-1234",
  "./single-story"
);

mdResult.logs.forEach((entry) => {
  console.log(`[${entry.level.toUpperCase()}] ${entry.message}`, ...entry.args);
});

if (!mdResult.result.success) {
  console.error("Import run failed", mdResult.result.errors);
}

if (exportAllResult.result.success) {
  console.log(`Exported ${exportAllResult.result.files.length} files to ${exportAllResult.result.outputDir}`);
} else {
  console.error("Bulk export failed", exportAllResult.result.errors);
}

if (!exportSingleResult.result.success) {
  console.error("Single story export failed", exportSingleResult.result.errors);
}

Examples

The examples/ workspace demonstrates end-to-end usage with ready-made scripts:

  • examples/md-to-project.ts — imports markdown from examples/md/ into a project.
  • examples/project-to-md.ts — exports project items into examples/items/.
  • examples/tests/ — Mocha scenarios that validate the flows.

Sample package.json scripts (from examples/package.json):

{
  "scripts": {
    "md": "ts-node ./md-to-project.ts",
    "project": "ts-node ./project-to-md.ts",
    "project:story": "ts-node ./project-to-md.ts --story"
  }
}

Run them from the examples/ directory once .env is configured:

npm run md            # imports multi-story markdown from examples/md/
npm run project       # exports all stories to examples/items/
npm run project:story # exports a single story, prompting when IDs are missing

Using project:story

npm run project:story -- Story-1234
  • Prompts for GitHub token and project ID if env vars GITHUB_TOKEN and PROJECT_ID are not set
  • Generates markdown for the specified story ID under stories/ by default
  • Accepts Story-XXXX via positional arg or --story Story-XXXX
  • Overrides the output directory via positional path or --output ./custom-dir

Parameter rules:

  • Story-ID positional detection checks for values that match /^Story-/i. If omitted, all stories are exported.
  • The first remaining positional argument is treated as the output directory. Without it, files are written to ./stories.
  • Flags --story=value / --output=value are equivalent to their spaced counterparts.

Examples:

npm run project -- Story-0456
npm run project ./stories/out-story -- Story-0112
npm run project:story -- Story-0112 ./stories/single
npm run project:story -- --story Story-0112 --output ./stories/single

How to get PROJECT_ID (personal GitHub user)

  • Create a new issue in your repository first
  • Then go to Projects settings -> Manage access, your GitHub username should appear with Admin role

PowerShell to query PROJECT_ID:

$owner = "your_github_username"
$repo = "your_repo_name"
$token = "your_github_token_with_repo_and_projects_access"
$headers = @{
    Authorization = "Bearer $token"
    "User-Agent"  = "PowerShell"
    Accept        = "application/json"
}

$query = @"
{
  repository(owner: "$owner", name: "$repo") {
    projectsV2(first: 10) {
      nodes {
        __typename
        id
        title
      }
    }
  }
}
"@

$body = @{ query = $query } | ConvertTo-Json -Depth 5 -Compress

$response = Invoke-RestMethod `
    -Uri "https://api.github.com/graphql" `
    -Method POST `
    -Headers $headers `
    -Body $body `
    -ContentType "application/json"

$response.errors
$response.data.repository.projectsV2.nodes | Select-Object id, title

API Reference

mdToProject(projectId: string, githubToken: string, sourcePath: string)

Import Multi-Story markdown files from a directory into a GitHub Project. Create-only and idempotent by Story ID.

  • projectId: GitHub Project V2 ID
  • githubToken: GitHub personal access token
  • sourcePath: Path to directory containing markdown files

projectToMd(projectId: string, githubToken: string, outputPath?: string)

Export GitHub Project items to Single-Story markdown files. Defaults to writing into ./stories when no output path is provided.

  • projectId: GitHub Project V2 ID
  • githubToken: GitHub personal access token
  • outputPath (optional): Output directory path. Defaults to './stories'

Story File Formats

Two complementary formats are supported:

  • Multi-Story files (for mdToProject() import)
  • Single-Story files (for projectToMd() export)

Multi-Story format (md→project)

Sections represent status. Each story must include - Story:, story id:, and description:.

## Backlog

- Story: Setup development environment
  Story ID: Story-001
  Description:
    - Install required tools
    - Configure IDE
    - Setup version control

## Ready

- Story: Implement authentication
  Story ID: Story-002
  Description:
    - Design flows
    - Implement backend
    - Integrate frontend

## In review

- Story: Improve accessibility
  Story ID: Story-003
  Description:
    - Audit key screens
    - Fix critical issues

Rules:

  • Allowed headings: Backlog, Ready, In progress, In review, Done
  • Aliases: To do → Ready, In Progress/in progress → In progress
  • Unrecognised headings map to Backlog
  • story id must be unique; existing IDs in Project are skipped (no update, no delete)
  • Within a file, duplicate IDs: only the first entry is honoured; later duplicates are skipped
  • description: content is free-form Markdown and preserved verbatim

Single-Story format (project→md, read-only)

Each file contains exactly one story and includes a Story ID section.

## Story: Setup development environment

### Story ID

Story-001

### Status

In progress

### Description

- Install required tools
- Configure IDE
- Setup version control

This format is generated by export and must not be used for import.

Status mapping

mdToProject() normalises headings/status strings using the logic in src/markdown-to-project.ts:

| Input heading / status | Stored status | | --- | --- | | Backlog | Backlog | | Ready, To do, Todo | Ready | | In progress, In Progress | In Progress | | In review | In review | | Done | Done | | Any other heading | Treated as Backlog |

Import and Export Behaviour

  • md→project (import)
    • Input: Multi-Story files only
    • Action: Create new items when story id does not exist in Project; skip otherwise
    • No updates or deletes from Markdown
  • project→md (export)
    • Output: Multiple Single-Story files, each with ### Story ID
    • Read-only: do not feed these files back into import

Limitations and caveats

  • The importer is create-only. Updating or deleting existing project items must be done in GitHub Projects.
  • Exporters overwrite files with the same name inside the target directory.
  • All commands expect PROJECT_ID and GITHUB_TOKEN to be available; the GitHub token must allow Projects and repo read access.
  • Large exports/imports may trigger GitHub API rate limits. Use --dry-run to validate before executing.
  • story id matching is case-insensitive, but duplicates in the same markdown file keep only the first occurrence.

Story ID

  • Matching uses Story ID only; titles never overwrite existing items
  • If an item with the same ID exists in Project: skip
  • Missing story id: strictly skipped and logged with file name, start line, and title
  • Missing ID plus exact title match triggers an additional "Possible title duplicate" warning

Dry-run and Diagnostics

Use dry-run to preview planned operations, with logs covering create plans, skip reasons, missing IDs, duplicates, and unknown keys. Ideal for CI gates and author feedback.

Migration (≤0.1.10 → 0.1.11)

  • Move from per-story import files to a Multi-Story import file
  • Ensure each story has a unique story id
  • Keep edits and deletions within GitHub Project; do not attempt to overwrite via Markdown
  • Update scripts or automation to use the new CLI entry points

Deprecated

  • src/story-to-project-item.ts is deprecated as an import entry. Use src/markdown-to-project.ts via the CLI or library.

GitHub Actions

MD Sync

name: MD Sync
on:
  push:
    paths:
      - examples/md/**/*.md
  workflow_dispatch:
jobs:
  sync:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm
      - run: npm ci
      - env:
          PROJECT_ID: ${{ secrets.PROJECT_ID }}
          GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
        run: npx ts-node examples/md-to-project.ts

Daily Project to MD

name: Daily Project to MD
on:
  schedule:
    - cron: "0 16 * * *"
  workflow_dispatch:
jobs:
  export:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm
      - run: npm ci
      - env:
          PROJECT_ID: ${{ secrets.PROJECT_ID }}
          GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
        run: npx ts-node examples/project-to-md.ts examples/items

Notes

  • Requires Node.js 18+
  • Runs in Node.js/server environments, not in the browser

Feedback

If you encounter any problems during use, or have suggestions for improvement, feel free to contact me:

You are also welcome to submit feedback directly in GitHub Issues 🙌


If you find this tool helpful, please consider giving it a ⭐️ Star on GitHub to support the project, or connect with me on LinkedIn.