Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
name: CI

on:
pull_request:
push:
branches:
- main

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
checks:
name: Checks / Node ${{ matrix.node-version }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
node-version:
- 20.x
- 22.x
- 24.x
steps:
- name: Checkout
uses: actions/checkout@v7

- name: Setup pnpm
uses: pnpm/action-setup@v6

- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version: ${{ matrix.node-version }}
cache: pnpm
cache-dependency-path: pnpm-lock.yaml

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Check formatting
run: pnpm format:check

- name: Lint
run: pnpm lint

- name: Typecheck
run: pnpm typecheck

- name: Test
run: pnpm test

- name: Build
run: pnpm build

pandoc-integration:
name: Pandoc Integration
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v7

- name: Setup pnpm
uses: pnpm/action-setup@v6

- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version: 22.x
cache: pnpm
cache-dependency-path: pnpm-lock.yaml

- name: Setup Pandoc
uses: pandoc/actions/setup@v1
with:
version: 3.10.1

- name: Verify Pandoc
run: pandoc --version

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Run Pandoc-backed tests
run: pnpm test
env:
POLYDOC_REQUIRE_PANDOC: "1"
62 changes: 59 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ taking on a CLI.
The original Polydoc design work scoped a local-first Markdown-to-Word and
Markdown-to-Google Docs workflow. This package keeps only the reusable library
boundary from that work. CLI commands, project manifests, watch mode, OAuth user
experience, transports, and sidecar storage stay outside this repository.
experience, remote service integrations, and sidecar storage stay outside this
repository. The library does include the shared transport contract and a local
file transport for consumers that need deterministic DOCX writes.

## API

Expand Down Expand Up @@ -45,6 +47,53 @@ console.log(SUPPORTED_PANDOC_MAJOR); // 3
write those bytes to disk, upload them to a transport, or pass them to another
library.

## Transports

Transports are side-effecting adapters that publish DOCX bytes for a canonical
document ID and return a stable destination handle for later consumers.

```ts
import {
LocalFileTransport,
convertMarkdownToDocx,
} from "@agentic-tooling/polydoc-core";

const docx = await convertMarkdownToDocx({
markdown,
referenceDocxPath: "./reference.docx",
});

const transport = new LocalFileTransport({ rootDir: "./generated-docx" });
const destination = await transport.upload("teamwiki/basic-note", docx);

console.log(destination.destinationId); // absolute path to generated-docx/teamwiki/basic-note.docx
console.log(destination.path); // same path, for local-file consumers
```

`LocalFileTransport` writes or overwrites one deterministic `.docx` destination
per canonical ID, creating parent directories as needed. By default,
`teamwiki/basic-note` maps to `<rootDir>/teamwiki/basic-note.docx`. A custom
`mapCanonicalId` option can map opaque IDs, such as `urn:teamwiki:note:123`, to
a relative destination under the same root.

The local file transport validates paths lexically before writing:

- Canonical IDs must be non-empty trimmed identifiers and must not contain NUL
bytes.
- Mapped destinations must be non-empty relative paths.
- Parent-directory segments, absolute paths, Windows drive syntax, backslashes,
colons, Windows-invalid filename characters, control characters, empty path
segments, Windows-reserved device names, path segments ending in dot or space,
and NUL bytes are rejected in mapped destinations.
- The resolved destination must stay under the configured `rootDir`.

This boundary check prevents accidental lexical path escape. It does not resolve
symlinks or claim protection against a writable root that already contains
hostile symlinks.

Transport failures throw `TransportError` with a stable `code`, actionable
`guidance`, and the original `cause` when filesystem or mapper operations fail.

## Pandoc Contract

This package shells out to the system `pandoc` binary through `execa` with an
Expand Down Expand Up @@ -103,7 +152,7 @@ Pandoc binary/version are expected to produce identical bytes.
## Requirements

- Node.js 20 or newer
- pnpm 11.9.0
- pnpm 10.34.5
- Pandoc 3.x for conversion

## Development
Expand All @@ -130,7 +179,14 @@ pnpm format:check
```

Pandoc integration tests are included in `pnpm test`. They skip cleanly when a
supported Pandoc binary is unavailable.
supported Pandoc binary is unavailable. CI has a dedicated Pandoc-backed job
that installs Pandoc 3.10.1 and requires the integration path to be available,
while the regular Node matrix can still run without Pandoc.

GitHub Actions runs blocking checks for pushes to `main` and pull requests:
format check, lint, typecheck, tests, and build across supported Node majors
20.x, 22.x, and 24.x. Dependency installation uses the checked-in lockfile with
`pnpm install --frozen-lockfile`.

Format files:

Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"type": "module",
"license": "MIT",
"author": "Logan Lindquist Land",
"packageManager": "pnpm@11.9.0",
"packageManager": "pnpm@10.34.5",
"engines": {
"node": ">=20"
},
Expand Down Expand Up @@ -45,7 +45,7 @@
"lint": "biome check .",
"format": "biome format --write .",
"format:check": "biome format .",
"check": "pnpm lint && pnpm typecheck && pnpm test && pnpm build",
"check": "biome check . && tsc -p tsconfig.json --noEmit && vitest run && tsc -p tsconfig.build.json",
"prepack": "pnpm build"
},
"devDependencies": {
Expand Down
9 changes: 9 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,12 @@ export {
PandocError,
SUPPORTED_PANDOC_MAJOR,
} from "./pandoc.js";
export type {
LocalFileDestinationMapper,
LocalFileTransportDestination,
LocalFileTransportOptions,
Transport,
TransportErrorCode,
TransportUploadResult,
} from "./transport.js";
export { LocalFileTransport, TransportError } from "./transport.js";
Loading