@stackbox/cms 0.0.4 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/dist/blocks.d.ts +18 -0
  2. package/dist/blocks.d.ts.map +1 -0
  3. package/dist/blocks.js +25 -0
  4. package/dist/blocks.js.map +1 -0
  5. package/dist/index.d.ts +12 -0
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +12 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/link.d.ts +10 -0
  10. package/dist/link.d.ts.map +1 -0
  11. package/dist/link.js +128 -0
  12. package/dist/link.js.map +1 -0
  13. package/dist/modules/blog/module.d.ts +20 -0
  14. package/dist/modules/blog/module.d.ts.map +1 -0
  15. package/dist/modules/blog/module.js +52 -0
  16. package/dist/modules/blog/module.js.map +1 -0
  17. package/dist/modules/blog/posts.d.ts +15 -0
  18. package/dist/modules/blog/posts.d.ts.map +1 -0
  19. package/dist/modules/blog/posts.js +46 -0
  20. package/dist/modules/blog/posts.js.map +1 -0
  21. package/dist/modules.d.ts +18 -0
  22. package/dist/modules.d.ts.map +1 -0
  23. package/dist/modules.js +25 -0
  24. package/dist/modules.js.map +1 -0
  25. package/dist/pages.d.ts +55 -0
  26. package/dist/pages.d.ts.map +1 -0
  27. package/dist/pages.js +85 -0
  28. package/dist/pages.js.map +1 -0
  29. package/dist/plugins/blog/index.d.ts +20 -0
  30. package/dist/plugins/blog/index.d.ts.map +1 -0
  31. package/dist/plugins/blog/index.js +52 -0
  32. package/dist/plugins/blog/index.js.map +1 -0
  33. package/dist/plugins/blog/module.d.ts +20 -0
  34. package/dist/plugins/blog/module.d.ts.map +1 -0
  35. package/dist/plugins/blog/module.js +52 -0
  36. package/dist/plugins/blog/module.js.map +1 -0
  37. package/dist/plugins/blog/plugin.d.ts +20 -0
  38. package/dist/plugins/blog/plugin.d.ts.map +1 -0
  39. package/dist/plugins/blog/plugin.js +52 -0
  40. package/dist/plugins/blog/plugin.js.map +1 -0
  41. package/dist/plugins/blog/posts.d.ts +15 -0
  42. package/dist/plugins/blog/posts.d.ts.map +1 -0
  43. package/dist/plugins/blog/posts.js +46 -0
  44. package/dist/plugins/blog/posts.js.map +1 -0
  45. package/dist/plugins/random-quote/index.d.ts +12 -0
  46. package/dist/plugins/random-quote/index.d.ts.map +1 -0
  47. package/dist/plugins/random-quote/index.js +21 -0
  48. package/dist/plugins/random-quote/index.js.map +1 -0
  49. package/dist/plugins/random-quote/module.d.ts +9 -0
  50. package/dist/plugins/random-quote/module.d.ts.map +1 -0
  51. package/dist/plugins/random-quote/module.js +31 -0
  52. package/dist/plugins/random-quote/module.js.map +1 -0
  53. package/dist/plugins/random-quote/plugin.d.ts +8 -0
  54. package/dist/plugins/random-quote/plugin.d.ts.map +1 -0
  55. package/dist/plugins/random-quote/plugin.js +31 -0
  56. package/dist/plugins/random-quote/plugin.js.map +1 -0
  57. package/dist/plugins/random-quote/public_assets/quotes.json +230 -0
  58. package/dist/plugins/random-quote/quotes.json +230 -0
  59. package/dist/public/_sb/plugins/random-quote/quotes.json +230 -0
  60. package/dist/render-head.d.ts +7 -0
  61. package/dist/render-head.d.ts.map +1 -0
  62. package/dist/render-head.js +27 -0
  63. package/dist/render-head.js.map +1 -0
  64. package/dist/render-page.d.ts +6 -0
  65. package/dist/render-page.d.ts.map +1 -0
  66. package/dist/render-page.js +30 -0
  67. package/dist/render-page.js.map +1 -0
  68. package/dist/routing.d.ts +3 -0
  69. package/dist/routing.d.ts.map +1 -0
  70. package/dist/routing.js +11 -0
  71. package/dist/routing.js.map +1 -0
  72. package/dist/site.d.ts +21 -0
  73. package/dist/site.d.ts.map +1 -0
  74. package/dist/site.js +102 -0
  75. package/dist/site.js.map +1 -0
  76. package/dist/slot-content.d.ts +30 -0
  77. package/dist/slot-content.d.ts.map +1 -0
  78. package/dist/slot-content.js +34 -0
  79. package/dist/slot-content.js.map +1 -0
  80. package/dist/slot-handle.d.ts +32 -0
  81. package/dist/slot-handle.d.ts.map +1 -0
  82. package/dist/slot-handle.js +68 -0
  83. package/dist/slot-handle.js.map +1 -0
  84. package/dist/stackbox/context.d.ts +52 -0
  85. package/dist/stackbox/context.d.ts.map +1 -0
  86. package/dist/stackbox/context.js +165 -0
  87. package/dist/stackbox/context.js.map +1 -0
  88. package/dist/templates.d.ts +63 -0
  89. package/dist/templates.d.ts.map +1 -0
  90. package/dist/templates.js +106 -0
  91. package/dist/templates.js.map +1 -0
  92. package/package.json +7 -2
  93. package/.claude/settings.local.json +0 -8
  94. package/scripts/copy-plugin-assets.mjs +0 -30
  95. package/src/blocks.ts +0 -58
  96. package/src/index.ts +0 -67
  97. package/src/link.ts +0 -158
  98. package/src/pages.ts +0 -198
  99. package/src/render-head.ts +0 -32
  100. package/src/render-page.ts +0 -51
  101. package/src/routing.ts +0 -14
  102. package/src/site.ts +0 -160
  103. package/src/slot-content.ts +0 -77
  104. package/src/slot-handle.ts +0 -131
  105. package/src/stackbox/context.ts +0 -257
  106. package/src/templates.ts +0 -230
  107. package/test/async-render.test.ts +0 -103
  108. package/test/blog.test.ts +0 -93
  109. package/test/fetch-routing.test.ts +0 -76
  110. package/test/plugin-assets.test.ts +0 -80
  111. package/test/random-quote.test.ts +0 -55
  112. package/test/stackbox-context.test.ts +0 -44
  113. package/tsconfig.json +0 -20
@@ -1,103 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { describe, it } from "node:test";
3
- import { html, renderAsync } from "@hyperspan/html";
4
- import { createBlock } from "../src/blocks.js";
5
- import { createPage } from "../src/pages.js";
6
- import { renderPage } from "../src/render-page.js";
7
- import { createSiteConfig } from "../src/site.js";
8
- import { renderSlotContent } from "../src/slot-handle.js";
9
- import { createTemplate } from "../src/templates.js";
10
-
11
- function sleep(ms: number): Promise<void> {
12
- return new Promise((resolve) => setTimeout(resolve, ms));
13
- }
14
-
15
- describe("async block rendering", () => {
16
- it("await async blocks in slot content", async () => {
17
- const siteConfig = createSiteConfig({ name: "Test" });
18
- const ctx = { siteConfig };
19
-
20
- const slowBlock = createBlock({
21
- name: "slow",
22
- async render() {
23
- await sleep(25);
24
- return html`<p>slow block</p>`;
25
- },
26
- })();
27
-
28
- const htmlOut = await renderAsync(
29
- await renderSlotContent("content", [slowBlock], ctx, undefined),
30
- );
31
-
32
- assert.match(htmlOut, /<p>slow block<\/p>/);
33
- assert.doesNotMatch(htmlOut, /hs:loading/);
34
- });
35
-
36
- it("resolves multiple async blocks concurrently", async () => {
37
- const siteConfig = createSiteConfig({ name: "Test" });
38
- const ctx = { siteConfig };
39
- const order: string[] = [];
40
-
41
- const first = createBlock({
42
- name: "first",
43
- async render() {
44
- await sleep(30);
45
- order.push("first");
46
- return html`<p>first</p>`;
47
- },
48
- })();
49
-
50
- const second = createBlock({
51
- name: "second",
52
- async render() {
53
- await sleep(10);
54
- order.push("second");
55
- return html`<p>second</p>`;
56
- },
57
- })();
58
-
59
- const htmlOut = await renderAsync(
60
- await renderSlotContent(
61
- "content",
62
- ["<p>before</p>", first, second],
63
- ctx,
64
- undefined,
65
- ),
66
- );
67
-
68
- assert.match(htmlOut, /<p>before<\/p>/);
69
- assert.match(htmlOut, /<p>first<\/p>/);
70
- assert.match(htmlOut, /<p>second<\/p>/);
71
- assert.deepEqual(order, ["second", "first"]);
72
- });
73
-
74
- it("renders async blocks through the full page pipeline", async () => {
75
- const siteConfig = createSiteConfig({ name: "Test Site" });
76
- const template = createTemplate({
77
- siteConfig,
78
- slots: [{ name: "content", options: { required: true, primary: true } }],
79
- render({ slots }: { slots: { content: { render: () => unknown } } }): ReturnType<typeof html> {
80
- return html`<main>${slots.content.render()}</main>`;
81
- },
82
- });
83
-
84
- const asyncBlock = createBlock({
85
- name: "page-async",
86
- async render() {
87
- await sleep(15);
88
- return html`<p>async page content</p>`;
89
- },
90
- })();
91
-
92
- const page = createPage(template, {
93
- path: "/async-test",
94
- title: "Async test",
95
- slots: { content: [asyncBlock] },
96
- });
97
-
98
- const htmlOut = await renderPage(page, siteConfig);
99
-
100
- assert.match(htmlOut, /<main><p>async page content<\/p><\/main>/);
101
- assert.doesNotMatch(htmlOut, /hs:loading/);
102
- });
103
- });
package/test/blog.test.ts DELETED
@@ -1,93 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { mkdirSync, writeFileSync } from "node:fs";
3
- import { join } from "node:path";
4
- import { mkdtempSync } from "node:fs";
5
- import { tmpdir } from "node:os";
6
- import { describe, it } from "node:test";
7
- import { createBlog } from "../src/plugins/blog/index.js";
8
-
9
- function makeBlogDir(posts: Record<string, string>): string {
10
- const root = mkdtempSync(join(tmpdir(), "stackbox-blog-"));
11
- const contentPath = join(root, "content");
12
- mkdirSync(contentPath, { recursive: true });
13
- for (const [filename, body] of Object.entries(posts)) {
14
- writeFileSync(join(contentPath, filename), body, "utf8");
15
- }
16
- return contentPath;
17
- }
18
-
19
- describe("createBlog", () => {
20
- it("returns post content objects with path, title, meta, and content", () => {
21
- const contentPath = makeBlogDir({
22
- "hello.md": `---
23
- title: Hello World
24
- description: A greeting post
25
- slug: hello
26
- ---
27
- <p>Hello body</p>`,
28
- });
29
-
30
- const blog = createBlog({ contentPath, pathPrefix: "/blog" });
31
-
32
- assert.strictEqual(blog.posts.length, 1);
33
- const post = blog.posts[0]!;
34
- assert.strictEqual(post.path, "/blog/hello");
35
- assert.strictEqual(post.title, "Hello World");
36
- assert.deepEqual(post.meta, { description: "A greeting post" });
37
- assert.strictEqual(post.content.length, 1);
38
- assert.match(String(post.content[0]), /data-slug="hello"/);
39
- assert.match(String(post.content[0]), /<h1>Hello World<\/h1>/);
40
- assert.match(String(post.content[0]), /<p>Hello body<\/p>/);
41
- });
42
-
43
- it("returns a single listing page with normalized path and post links", () => {
44
- const contentPath = makeBlogDir({
45
- "a.md": "---\ntitle: Post A\n---\nBody A",
46
- "b.md": "---\ntitle: Post B\n---\nBody B",
47
- });
48
-
49
- const blog = createBlog({ contentPath, pathPrefix: "/blog/" });
50
-
51
- assert.strictEqual(blog.listings.length, 1);
52
- assert.strictEqual(blog.listings[0]!.path, "/blog");
53
- assert.strictEqual(blog.listings[0]!.content.length, 1);
54
- const html = String(blog.listings[0]!.content[0]);
55
- assert.match(html, /<a href="\/blog\/a">Post A<\/a>/);
56
- assert.match(html, /<a href="\/blog\/b">Post B<\/a>/);
57
- });
58
-
59
- it("returns empty listing message when there are no posts", () => {
60
- const contentPath = makeBlogDir({});
61
-
62
- const blog = createBlog({ contentPath, pathPrefix: "/blog" });
63
-
64
- assert.strictEqual(blog.posts.length, 0);
65
- assert.strictEqual(blog.listings.length, 1);
66
- assert.match(String(blog.listings[0]!.content[0]), /No blog posts found/);
67
- });
68
-
69
- it("returns multiple listing pages when postsPerPage is set", () => {
70
- const contentPath = makeBlogDir({
71
- "a.md": "---\ntitle: Post A\n---\n",
72
- "b.md": "---\ntitle: Post B\n---\n",
73
- "c.md": "---\ntitle: Post C\n---\n",
74
- });
75
-
76
- const blog = createBlog({
77
- contentPath,
78
- pathPrefix: "/blog",
79
- postsPerPage: 2,
80
- });
81
-
82
- assert.strictEqual(blog.listings.length, 2);
83
- assert.strictEqual(blog.listings[0]!.path, "/blog");
84
- assert.strictEqual(blog.listings[1]!.path, "/blog/page/2");
85
-
86
- const page1 = String(blog.listings[0]!.content[0]);
87
- const page2 = String(blog.listings[1]!.content[0]);
88
- assert.match(page1, /Post A/);
89
- assert.match(page1, /Post B/);
90
- assert.doesNotMatch(page1, /Post C/);
91
- assert.match(page2, /Post C/);
92
- });
93
- });
@@ -1,76 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { describe, it } from "node:test";
3
- import { html } from "@hyperspan/html";
4
- import { createPage } from "../src/pages.js";
5
- import { createSite, createSiteConfig } from "../src/site.js";
6
- import { createTemplate } from "../src/templates.js";
7
-
8
- function createFixtureSite() {
9
- const siteConfig = createSiteConfig({ name: "Routing Test" });
10
-
11
- const template = createTemplate({
12
- siteConfig,
13
- slots: [{ name: "content", options: { required: true, primary: true } }],
14
- render({ slots }: { slots: { content: { render: () => unknown } } }): ReturnType<typeof html> {
15
- return html`<body>${slots.content.render()}</body>`;
16
- },
17
- });
18
-
19
- const homePage = createPage(template, {
20
- path: "/",
21
- title: "Home",
22
- slots: { content: ["<p>home</p>"] },
23
- });
24
-
25
- const aboutPage = createPage(template, {
26
- path: "/about",
27
- title: "About",
28
- slots: { content: ["<p>about</p>"] },
29
- });
30
-
31
- return createSite(siteConfig, { pages: [homePage, aboutPage] });
32
- }
33
-
34
- describe("site.fetch routing", () => {
35
- it("routes / and /about to correct pages", async () => {
36
- const site = createFixtureSite();
37
-
38
- const home = await site.fetch(new Request("https://example.com/"));
39
- assert.strictEqual(home.status, 200);
40
- assert.match(await home.text(), /<p>home<\/p>/);
41
-
42
- const about = await site.fetch(new Request("https://example.com/about"));
43
- assert.strictEqual(about.status, 200);
44
- assert.match(await about.text(), /<p>about<\/p>/);
45
- });
46
-
47
- it("normalizes trailing slashes", async () => {
48
- const site = createFixtureSite();
49
- const res = await site.fetch(new Request("https://example.com/about/"));
50
- assert.strictEqual(res.status, 200);
51
- assert.match(await res.text(), /<p>about<\/p>/);
52
- });
53
-
54
- it("returns 404 for unknown pages", async () => {
55
- const site = createFixtureSite();
56
- const res = await site.fetch(new Request("https://example.com/missing"));
57
- assert.strictEqual(res.status, 404);
58
- });
59
-
60
- it("returns 405 for POST", async () => {
61
- const site = createFixtureSite();
62
- const res = await site.fetch(
63
- new Request("https://example.com/", { method: "POST" }),
64
- );
65
- assert.strictEqual(res.status, 405);
66
- });
67
-
68
- it("returns 200 with empty body for HEAD", async () => {
69
- const site = createFixtureSite();
70
- const res = await site.fetch(
71
- new Request("https://example.com/", { method: "HEAD" }),
72
- );
73
- assert.strictEqual(res.status, 200);
74
- assert.strictEqual(await res.text(), "");
75
- });
76
- });
@@ -1,80 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { existsSync } from "node:fs";
3
- import { join } from "node:path";
4
- import { describe, it } from "node:test";
5
- import {
6
- getPluginPublicRootDir,
7
- parsePluginAssetRequest,
8
- pluginAssetPath,
9
- servePluginAsset,
10
- } from "../src/link.js";
11
- import { createSite, createSiteConfig } from "../src/site.js";
12
- import { createPage } from "../src/pages.js";
13
- import { createTemplate } from "../src/templates.js";
14
- import { html } from "@hyperspan/html";
15
-
16
- describe("pluginAssetPath", () => {
17
- it("builds paths under /_sb/plugins", () => {
18
- assert.strictEqual(
19
- pluginAssetPath("random-quote", "photo.jpg"),
20
- "/_sb/plugins/random-quote/photo.jpg",
21
- );
22
- });
23
-
24
- it("rejects unsafe relative segments", () => {
25
- assert.throws(() => pluginAssetPath("random-quote", "../secret.txt"));
26
- });
27
- });
28
-
29
- describe("plugin public assets", () => {
30
- it("copies random-quote quotes.json to dist/public/_sb", () => {
31
- const file = join(
32
- getPluginPublicRootDir(),
33
- "random-quote",
34
- "quotes.json",
35
- );
36
- assert.ok(existsSync(file));
37
- });
38
-
39
- it("parsePluginAssetRequest rejects traversal", () => {
40
- assert.strictEqual(
41
- parsePluginAssetRequest("/_sb/plugins/random-quote/../quotes.json"),
42
- null,
43
- );
44
- });
45
-
46
- it("servePluginAsset returns JSON for bundled quotes", async () => {
47
- const res = servePluginAsset(
48
- "/_sb/plugins/random-quote/quotes.json",
49
- "GET",
50
- );
51
- assert.ok(res);
52
- assert.strictEqual(res!.status, 200);
53
- assert.match(res!.headers.get("content-type") ?? "", /json/);
54
- const body = await res!.text();
55
- assert.match(body, /Rumi/);
56
- });
57
-
58
- it("site.fetch serves plugin assets before page routes", async () => {
59
- const siteConfig = createSiteConfig({ name: "Asset Test" });
60
- const template = createTemplate({
61
- siteConfig,
62
- slots: [{ name: "content", options: { required: true, primary: true } }],
63
- render({ slots }: { slots: { content: { render: () => unknown } } }) {
64
- return html`<body>${slots.content.render()}</body>`;
65
- },
66
- });
67
- const homePage = createPage(template, {
68
- path: "/",
69
- title: "Home",
70
- slots: { content: ["<p>home</p>"] },
71
- });
72
- const site = createSite(siteConfig, { pages: [homePage] });
73
-
74
- const res = await site.fetch(
75
- new Request("https://example.com/_sb/plugins/random-quote/quotes.json"),
76
- );
77
- assert.strictEqual(res.status, 200);
78
- assert.match(await res.text(), /Rumi/);
79
- });
80
- });
@@ -1,55 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { describe, it } from "node:test";
3
- import { renderAsync } from "@hyperspan/html";
4
- import { createSiteConfig } from "../src/site.js";
5
- import {
6
- defaultQuotes,
7
- pickRandomQuote,
8
- randomQuoteBlock,
9
- type Quote,
10
- } from "../src/plugins/random-quote/index.js";
11
-
12
- describe("random-quote plugin", () => {
13
- it("ships bundled default quotes", () => {
14
- assert.ok(defaultQuotes.length >= 50);
15
- assert.strictEqual(defaultQuotes[0]!.author, "Rumi");
16
- });
17
-
18
- it("pickRandomQuote returns a quote from the given list", () => {
19
- const custom: Quote[] = [
20
- { text: "Custom quote.", author: "Test Author" },
21
- ];
22
- const quote = pickRandomQuote(custom);
23
- assert.strictEqual(quote.author, "Test Author");
24
- });
25
-
26
- it("randomQuoteBlock uses bundled quotes by default", async () => {
27
- const siteConfig = createSiteConfig({ name: "Test" });
28
- const originalRandom = Math.random;
29
- Math.random = () => 0;
30
-
31
- try {
32
- const block = randomQuoteBlock();
33
- const htmlOut = await renderAsync(block.render({ siteConfig }));
34
-
35
- assert.match(htmlOut, /data-block="random-quote"/);
36
- assert.match(htmlOut, /<blockquote/);
37
- assert.match(htmlOut, /beat 40 scholars/);
38
- assert.match(htmlOut, /— Rumi/);
39
- } finally {
40
- Math.random = originalRandom;
41
- }
42
- });
43
-
44
- it("randomQuoteBlock accepts custom quotes", async () => {
45
- const siteConfig = createSiteConfig({ name: "Test" });
46
- const custom: Quote[] = [
47
- { text: "Ship your own quotes.", author: "Site Owner" },
48
- ];
49
- const block = randomQuoteBlock({ quotes: custom });
50
- const htmlOut = await renderAsync(block.render({ siteConfig }));
51
-
52
- assert.match(htmlOut, /Ship your own quotes/);
53
- assert.match(htmlOut, /— Site Owner/);
54
- });
55
- });
@@ -1,44 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { describe, it } from "node:test";
3
- import { createContext } from "../src/stackbox/context.js";
4
-
5
- describe("Stackbox createContext", () => {
6
- it("wraps request with uppercase method and query params", async () => {
7
- const raw = new Request("https://example.com/path?foo=bar", {
8
- method: "get",
9
- headers: { Cookie: "session=abc123" },
10
- });
11
- const ctx = createContext(raw, { API_KEY: "secret" });
12
-
13
- assert.strictEqual(ctx.req.method, "GET");
14
- assert.strictEqual(ctx.req.query.get("foo"), "bar");
15
- assert.strictEqual(ctx.req.cookies.get("session"), "abc123");
16
- assert.strictEqual(ctx.env.API_KEY, "secret");
17
- });
18
-
19
- it("ctx.res.html produces a valid Response", async () => {
20
- const ctx = createContext(new Request("https://example.com/"), {});
21
- const res = await ctx.res.html("<p>hello</p>");
22
-
23
- assert.strictEqual(res.status, 200);
24
- assert.match(await res.text(), /<p>hello<\/p>/);
25
- assert.match(res.headers.get("Content-Type") ?? "", /text\/html/);
26
- });
27
-
28
- it("ctx.res.json produces a valid Response", async () => {
29
- const ctx = createContext(new Request("https://example.com/"), {});
30
- const res = await ctx.res.json({ ok: true });
31
-
32
- assert.strictEqual(res.status, 200);
33
- assert.deepEqual(await res.json(), { ok: true });
34
- });
35
-
36
- it("sets cookies on outgoing response", async () => {
37
- const ctx = createContext(new Request("https://example.com/"), {});
38
- ctx.res.cookies.set("theme", "dark", { path: "/" });
39
- const res = await ctx.res.text("ok");
40
-
41
- const setCookie = res.headers.get("Set-Cookie") ?? res.headers.get("set-cookie");
42
- assert.ok(setCookie?.includes("theme=dark"));
43
- });
44
- });
package/tsconfig.json DELETED
@@ -1,20 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "NodeNext",
5
- "moduleResolution": "NodeNext",
6
- "lib": ["ES2022", "DOM"],
7
- "rootDir": "src",
8
- "outDir": "dist",
9
- "declaration": true,
10
- "declarationMap": true,
11
- "sourceMap": true,
12
- "strict": true,
13
- "skipLibCheck": true,
14
- "esModuleInterop": true,
15
- "resolveJsonModule": true,
16
- "types": ["node"]
17
- },
18
- "include": ["src/**/*.ts"],
19
- "exclude": ["dist", "node_modules"]
20
- }