Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move to TypeScript #78

Merged
merged 22 commits into from
Dec 26, 2024
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
97a71de
feat(ts): initial move to typescript (no tests)
vernak2539 Dec 19, 2024
458c5aa
feat(ts): update type of default export
vernak2539 Dec 19, 2024
418bf34
feat(ts): add tsx as dep to run node test runner with typescript support
vernak2539 Dec 19, 2024
ddf05dc
feat(ts): covert test files to ts
vernak2539 Dec 19, 2024
681519b
feat(ts): udpate test files and get runner working
vernak2539 Dec 19, 2024
86d51f8
feat(ts): build package before publish
vernak2539 Dec 19, 2024
4581855
feat(ts): upgrade typedoc and deps
vernak2539 Dec 19, 2024
4a2fb49
feat(ts): add build command to tests
vernak2539 Dec 19, 2024
95a9491
feat(ts): move build to own job
vernak2539 Dec 19, 2024
6df53c4
feat(ts): update name of test+build gh action
vernak2539 Dec 19, 2024
bcea29a
fix(docs): point typedoc at index.ts + export option type again
vernak2539 Dec 19, 2024
bdb8f1a
fix(docs): add back description
vernak2539 Dec 19, 2024
3ba0bec
fix(tsconfig): update tsconfig.json
vernak2539 Dec 20, 2024
1c56336
fix(structure): move index to plugin + use index for exports
vernak2539 Dec 20, 2024
ee1835d
fix(types): export CollectionConfig
vernak2539 Dec 20, 2024
8789e23
chore: install and use typedoc-plugin-zod
vernak2539 Dec 20, 2024
c050656
chore: allow back ts extensions
vernak2539 Dec 20, 2024
007a526
fix(tests): fix tests
vernak2539 Dec 20, 2024
cbf99fe
fix(docs): remove old jsdoc + update barrel file
vernak2539 Dec 26, 2024
7a0e4fa
fix(tsconfig): update tsconfig for typedoc
vernak2539 Dec 26, 2024
dea8cf8
fix: add checks for potentially non-existing variables
vernak2539 Dec 26, 2024
b830c6e
fix(docs): link to options type in docs
vernak2539 Dec 26, 2024
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
2 changes: 2 additions & 0 deletions .github/workflows/npm-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ jobs:
run: rm -rf docs CHANGELOG.md
- name: Install Dependencies
run: yarn --immutable
- name: Build
run: yarn build
- name: Publish
run: yarn npm publish
env:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs

name: Run Tests
name: Run Tests + Build

on:
push:
Expand All @@ -10,6 +10,17 @@ on:
branches: ["main"]

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Use Node.js 20.x
uses: actions/setup-node@v4
with:
node-version: "20.x" # needs to be the same version as when publishing
cache: "yarn"
- run: yarn
- run: yarn build
test:
strategy:
matrix:
Expand All @@ -25,3 +36,4 @@ jobs:
cache: "yarn"
- run: yarn
- run: yarn test

1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ oldtest.mjs
!.yarn/releases
!.yarn/plugins
.pnp.*
dist
21 changes: 10 additions & 11 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,11 @@
"relative links"
],
"homepage": "https://github.com/vernak2539/astro-rehype-relative-markdown-links",
"main": "./src/index.mjs",
"type": "module",
"types": "./src/index.d.ts",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"files": [
"src/index.mjs",
"src/utils.mjs",
"src/options.mjs",
"src/**/*.d.ts"
"dist"
],
"repository": {
"type": "git",
Expand All @@ -30,10 +27,10 @@
},
"packageManager": "[email protected]",
"scripts": {
"pre-release": "yarn run changelog && yarn run prettier && yarn run generate-docs",
"build": "tsup",
"generate-docs": "typedoc",
"prettier": "prettier ./src/** -w",
"test": "ARRML_MATTER_CACHE_DISABLE=true node --loader=esmock --test",
"test": "ARRML_MATTER_CACHE_DISABLE=true node --import tsx --loader=esmock --test src/**/*.test.ts",
"type-check": "tsc --noEmit --emitDeclarationOnly false",
"prepare": "husky"
},
Expand All @@ -59,9 +56,11 @@
"prettier": "^4.0.0-alpha.8",
"rehype": "^13.0.2",
"remark-toc": "^9.0.0",
"typedoc": "^0.27.4",
"typedoc-plugin-markdown": "^4.3.2",
"typedoc-plugin-remark": "^1.2.0",
"tsup": "^8.3.5",
"tsx": "^4.19.2",
"typedoc": "^0.27.5",
"typedoc-plugin-markdown": "^4.3.3",
"typedoc-plugin-remark": "^1.2.1",
"typescript": "^5.7.2"
}
}
15 changes: 0 additions & 15 deletions src/index.d.ts

This file was deleted.

12 changes: 6 additions & 6 deletions src/index.test.mjs → src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,18 @@ import path, { dirname } from "path";
import { rehype } from "rehype";
import { visit } from "unist-util-visit";
import esmock from "esmock";
import { validateOptions as validateOptionsOriginal } from "./options.mjs";
import { validateOptions as validateOptionsOriginal } from "./options";

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

import astroRehypeRelativeMarkdownLinks from "./index.mjs";
import astroRehypeRelativeMarkdownLinks from "./index";

/*
NOTE ON ESMOCK USAGE

node:test does not provide a stock way of mocking sub-modules. There is work being done on this (see
links below) but for now some type of module loader is required. Esmock (https://github.com/iambumblehead/esmock)
links below) but for now some type of module loader is required. Esmock (https://github.com/iambumblehead/esmock)
seems to address what is needed for our use cases for now although there doesn't seem to be a simple way for a simple spy
as you need to swap in the original manually. If/When node:test supports this natively, esmock can be removed.

Expand Down Expand Up @@ -418,10 +418,10 @@ describe("astroRehypeRelativeMarkdownLinks", () => {
});

describe("config option validation", () => {
const runValidationTest = async (context, options) => {
const runValidationTest = async (context, options?) => {
const validateOptionsMock = context.mock.fn(validateOptionsOriginal);
const astroRehypeRelativeMarkdownLinksMock = await esmock("./index.mjs", {
"./options.mjs": {
const astroRehypeRelativeMarkdownLinksMock = await esmock("./index.ts", {
"./options.ts": {
validateOptions: validateOptionsMock,
},
});
Expand Down
31 changes: 20 additions & 11 deletions src/index.mjs → src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { visit } from "unist-util-visit";
import * as path from "path";
import { default as debugFn } from "debug";
import type { Plugin } from "unified";
import type { Root } from "hast";
import {
replaceExt,
isValidRelativeLink,
Expand All @@ -16,17 +18,24 @@ import {
shouldProcessFile,
getMatter,
resolveCollectionBase,
} from "./utils.mjs";
import { validateOptions, mergeCollectionOptions } from "./options.mjs";
} from "./utils";
import {
type Options,
validateOptions,
mergeCollectionOptions,
} from "./options";
vernak2539 marked this conversation as resolved.
Show resolved Hide resolved

// This package makes a lot of assumptions based on it being used with Astro

const debug = debugFn("astro-rehype-relative-markdown-links");

/**
* @type {import('unified').Plugin<[(import('./options.d.ts').Options | null | undefined)?], import('hast').Root>}
* Rehype plugin for Astro to add support for transforming relative links in MD and MDX files into their final page paths.
vernak2539 marked this conversation as resolved.
Show resolved Hide resolved
*/
function astroRehypeRelativeMarkdownLinks(opts) {
const astroRehypeRelativeMarkdownLinks: Plugin<
[(Options | null | undefined)?],
Root
> = (opts) => {
const options = validateOptions(opts);

return (tree, file) => {
Expand Down Expand Up @@ -71,23 +80,23 @@ function astroRehypeRelativeMarkdownLinks(opts) {
content collections.

Given this, the default behavior we follow is to to derive the the collection name from physical name of the collection
directory on disk and include it in the generated path. Since we don't have internal info, we have to make an assumption
that the site page path is mapped directly to a path that starts with the collection name followed by the slug of the content
collection page. We do this by following Astro's own assumptions on the directory structure of content collections - they
directory on disk and include it in the generated path. Since we don't have internal info, we have to make an assumption
that the site page path is mapped directly to a path that starts with the collection name followed by the slug of the content
collection page. We do this by following Astro's own assumptions on the directory structure of content collections - they
are subdirectories of content path.

We make a couple of exceptions to the above based on options specified:

1. By default, we derive the name of the collection from the physical name of the content collection directory, however when
options.<collectionname>.name is specified, we use that value instead for the name of the collection.
2. When the effective `collectionBase` (options.collectionBase or options.collections.<collectionname>.base
is `false`), we treat the content collection as the site root so we do not include the effective content collection
name in the transformed url. For example, with a content collection page of of src/content/docs/page-1.md, if the
is `false`), we treat the content collection as the site root so we do not include the effective content collection
name in the transformed url. For example, with a content collection page of of src/content/docs/page-1.md, if the
effective collectionBase is `false`, the url would be `/page-1` whereas with effective collectionBase of `name`, it would be `/docs/page-1`.

KNOWN LIMITATIONS/ISSUES
- Astro allows mapping a content collection to multiple site paths (as mentioned above). The current approach of this library
assumes that page paths always align 1:1 to their corresponding content collection paths based on physical directory name (or custom slug).
assumes that page paths always align 1:1 to their corresponding content collection paths based on physical directory name (or custom slug).
For example, if you have a content collection at src/content/blogs but your site page path is at /pages/my-blog/[...slug].astro and you have not
overridden the collection name to `my-blog` via options.<collectionname>.name, the default functionality of this library will not work currently.
Note, that even when overridden via options, if you map multiple page paths to the same collection (e.g., /pages/my-blog/[...slug].astro &
Expand Down Expand Up @@ -197,6 +206,6 @@ function astroRehypeRelativeMarkdownLinks(opts) {
node.properties.href = webPathFinal;
});
};
}
};

export default astroRehypeRelativeMarkdownLinks;
21 changes: 0 additions & 21 deletions src/options.d.ts

This file was deleted.

13 changes: 8 additions & 5 deletions src/options.test.mjs → src/options.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { describe, test } from "node:test";
import { mergeCollectionOptions, validateOptions } from "./options.mjs";
import {
mergeCollectionOptions,
type Options,
type CollectionConfig,
validateOptions,
} from "./options";
import assert from "node:assert";

/** @type {import('./options.d.ts').CollectionConfig} */
const defaultCollectionConfig = {};
const defaultCollectionConfig: CollectionConfig = {};

/** @type {import('./options.d.ts').Options} */
const defaultOptions = {
const defaultOptions: Options = {
srcDir: "./src",
trailingSlash: "ignore",
collectionBase: "name",
Expand Down
23 changes: 19 additions & 4 deletions src/options.mjs → src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,21 @@ export const OptionsSchema = z.object({
.default("ignore"),
});

/** @type {import('./options.d.ts').ValidateOptions} */
export const validateOptions = (options) => {
/** Collection specific options */
type CollectionConfigSchemaType = typeof CollectionConfigSchema;
export interface CollectionConfig extends z.input<CollectionConfigSchemaType> {}

/** General options */
export type Options = z.infer<typeof OptionsSchema>;
interface EffectiveOptions extends Options {}
Copy link
Contributor

@techfg techfg Dec 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couple of things here:

  1. Options should use z.input while EffectiveOptions should use z.infer (which is the same as z.output)
  2. There are still some remaining issues with typedoc/typedoc-plugin-markdown/etc. when using types instead of interfaces. If you change Options and CollectionsSchema to interface and make all the other changes that I recommend, the docs should generate identical to current. However, we want Options to be a type to solve for the hover issue. So, we use the @interface typedoc tag as a workaround for now. This is not ideal as the docs will have Options as an interface and we're going to lose the TOC, etc. but the code itself will be correct. I'm going to update the isuses I have filed with typedoc, etc. to cover a few additional situations that I found just now. Once all those issues are addressed, we can eliminate the @interface workaround.
type OptionsSchemaType = typeof OptionsSchema;
type CollectionConfigSchemaType = typeof CollectionConfigSchema;

/** 
 * Collection specific options 
 * @interface
 */
export type CollectionConfig = z.input<CollectionConfigSchemaType>;

/** 
 * General options 
 * @interface
 */
export type Options = z.input<OptionsSchemaType>;
interface EffectiveOptions extends z.infer<OptionsSchemaType> {};

EDIT: Per my recent comment, the @interface workaround should no longer be required and its inclusion can be ignored. The docs should generate correctly with Options and CollectionConfig as types assuming all the changes in other comments are implemented.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vernak2539 -

Sorry if I caused some confusion with my edit regarding @interface :( The part about needing the @interface tag still applies but the other changes are still needed and seem to have been missed:

  1. Options should use z.input - This ensures that the docs correctly reflect optional properties. Currently, for example, collectionBase does not show as optional.
  2. EffectiveOptions should use z.infer - These are the options after defaults are applied so for options that have a default, even if the user doesn't provide one, the effective options, which we use, have proper types based on presence of defaults.
  3. CollectionConfig should be a type - This ensures that "hover" works.

The final code block should be

/** Collection specific options */
export type CollectionConfig = z.input<CollectionConfigSchemaType>;
type CollectionConfigSchemaType = typeof CollectionConfigSchema;

/** General options */
export type Options = z.input<OptionsSchemaType>;
type OptionsSchemaType = typeof OptionsSchema;

interface EffectiveOptions extends z.infer<OptionsSchemaType> {};

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Fixed in #79

export interface EffectiveCollectionOptions
extends Omit<EffectiveOptions, "collections"> {
collectionName: string;
}

export const validateOptions = (
options: Options | null | undefined,
): EffectiveOptions => {
const result = OptionsSchema.safeParse(options || {});
if (!result.success) {
throw result.error;
Expand All @@ -122,8 +135,10 @@ export const validateOptions = (options) => {
return result.data;
};

/** @type {import('./options.d.ts').MergeCollectionOptions} */
export const mergeCollectionOptions = (collectionName, options) => {
export const mergeCollectionOptions = (
collectionName: string,
options: EffectiveOptions,
): EffectiveCollectionOptions => {
const config = options.collections[collectionName] || {};
const { base = options.collectionBase, name = collectionName } = config;
return {
Expand Down
30 changes: 0 additions & 30 deletions src/utils.d.ts

This file was deleted.

2 changes: 1 addition & 1 deletion src/utils.test.mjs → src/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
resolveSlug,
applyTrailingSlash,
resolveCollectionBase,
} from "./utils.mjs";
} from "./utils";

describe("replaceExt", () => {
test("replaces extension with another extension", () => {
Expand Down
Loading
Loading