@ibalzam/codejitsu-core 0.1.0
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.
- package/CLAUDE.md +67 -0
- package/LICENSE +21 -0
- package/MIGRATIONS/README.md +30 -0
- package/README.md +35 -0
- package/checklist/bin/run.mjs +189 -0
- package/checklist/core.md +55 -0
- package/modules/blog/CLAUDE.md +87 -0
- package/modules/blog/checklist.md +36 -0
- package/modules/blog/src/components/index.ts +1 -0
- package/modules/blog/src/index.ts +201 -0
- package/modules/blog/templates/content/_sample-post.md +18 -0
- package/modules/blog/templates/lib/blog.ts +17 -0
- package/modules/blog/templates/pages/blog/[...slug].astro +53 -0
- package/modules/blog/templates/pages/blog/category/[category].astro +40 -0
- package/modules/blog/templates/pages/blog/index.astro +30 -0
- package/modules/blog/templates/pages/blog/tag/[tag].astro +35 -0
- package/modules/deploy/CLAUDE.md +58 -0
- package/modules/deploy/checklist.md +9 -0
- package/modules/deploy/templates/daily-deploy.yml +29 -0
- package/modules/deploy/templates/wrangler.toml +2 -0
- package/modules/images/CLAUDE.md +77 -0
- package/modules/images/bin/optimize.mjs +44 -0
- package/modules/images/checklist.md +23 -0
- package/modules/images/src/index.ts +21 -0
- package/modules/images/src/optimize.mjs +113 -0
- package/modules/images/templates/codejitsu-images.config.mjs +18 -0
- package/modules/llms/CLAUDE.md +57 -0
- package/modules/llms/bin/generate.mjs +35 -0
- package/modules/llms/checklist.md +10 -0
- package/modules/llms/src/generate.mjs +179 -0
- package/modules/llms/templates/codejitsu-llms.config.mjs +39 -0
- package/modules/seo/CLAUDE.md +95 -0
- package/modules/seo/checklist.md +30 -0
- package/modules/seo/src/index.ts +2 -0
- package/modules/seo/src/schema.ts +203 -0
- package/modules/seo/src/sitemap.ts +109 -0
- package/modules/seo/templates/Head.astro +53 -0
- package/modules/seo/templates/robots.txt +5 -0
- package/package.json +73 -0
- package/src/index.ts +1 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON-LD schema.org builders. Each function returns a plain object ready
|
|
3
|
+
* to `JSON.stringify` and inject into a `<script type="application/ld+json">`.
|
|
4
|
+
*
|
|
5
|
+
* Helper at the bottom: `jsonLd(obj)` returns the stringified script body.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface OrganizationInput {
|
|
9
|
+
name: string;
|
|
10
|
+
url: string;
|
|
11
|
+
logo?: string;
|
|
12
|
+
sameAs?: string[];
|
|
13
|
+
email?: string;
|
|
14
|
+
telephone?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function organization(input: OrganizationInput) {
|
|
18
|
+
return {
|
|
19
|
+
'@context': 'https://schema.org',
|
|
20
|
+
'@type': 'Organization',
|
|
21
|
+
name: input.name,
|
|
22
|
+
url: input.url,
|
|
23
|
+
...(input.logo && { logo: input.logo }),
|
|
24
|
+
...(input.sameAs && { sameAs: input.sameAs }),
|
|
25
|
+
...(input.email && { email: input.email }),
|
|
26
|
+
...(input.telephone && { telephone: input.telephone }),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface PostalAddress {
|
|
31
|
+
streetAddress?: string;
|
|
32
|
+
addressLocality: string;
|
|
33
|
+
addressRegion?: string;
|
|
34
|
+
postalCode?: string;
|
|
35
|
+
addressCountry: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface GeoCoordinates {
|
|
39
|
+
latitude: number;
|
|
40
|
+
longitude: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface LocalBusinessInput {
|
|
44
|
+
name: string;
|
|
45
|
+
url: string;
|
|
46
|
+
telephone: string;
|
|
47
|
+
image?: string;
|
|
48
|
+
priceRange?: string;
|
|
49
|
+
address: PostalAddress;
|
|
50
|
+
geo?: GeoCoordinates;
|
|
51
|
+
openingHours?: string[];
|
|
52
|
+
areaServed?: string[];
|
|
53
|
+
sameAs?: string[];
|
|
54
|
+
/** Override @type to a more specific LocalBusiness subtype, e.g. 'HVACBusiness', 'HomeAndConstructionBusiness'. */
|
|
55
|
+
type?: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function localBusiness(input: LocalBusinessInput) {
|
|
59
|
+
return {
|
|
60
|
+
'@context': 'https://schema.org',
|
|
61
|
+
'@type': input.type ?? 'LocalBusiness',
|
|
62
|
+
name: input.name,
|
|
63
|
+
url: input.url,
|
|
64
|
+
telephone: input.telephone,
|
|
65
|
+
...(input.image && { image: input.image }),
|
|
66
|
+
...(input.priceRange && { priceRange: input.priceRange }),
|
|
67
|
+
address: { '@type': 'PostalAddress', ...input.address },
|
|
68
|
+
...(input.geo && { geo: { '@type': 'GeoCoordinates', ...input.geo } }),
|
|
69
|
+
...(input.openingHours && { openingHoursSpecification: input.openingHours }),
|
|
70
|
+
...(input.areaServed && { areaServed: input.areaServed }),
|
|
71
|
+
...(input.sameAs && { sameAs: input.sameAs }),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface WebSiteInput {
|
|
76
|
+
name: string;
|
|
77
|
+
url: string;
|
|
78
|
+
/** If set, adds SearchAction pointing to this URL template (with `{search_term_string}`). */
|
|
79
|
+
searchUrlTemplate?: string;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function website(input: WebSiteInput) {
|
|
83
|
+
const base: Record<string, unknown> = {
|
|
84
|
+
'@context': 'https://schema.org',
|
|
85
|
+
'@type': 'WebSite',
|
|
86
|
+
name: input.name,
|
|
87
|
+
url: input.url,
|
|
88
|
+
};
|
|
89
|
+
if (input.searchUrlTemplate) {
|
|
90
|
+
base.potentialAction = {
|
|
91
|
+
'@type': 'SearchAction',
|
|
92
|
+
target: { '@type': 'EntryPoint', urlTemplate: input.searchUrlTemplate },
|
|
93
|
+
'query-input': 'required name=search_term_string',
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
return base;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export interface BlogPostingInput {
|
|
100
|
+
title: string;
|
|
101
|
+
description: string;
|
|
102
|
+
url: string;
|
|
103
|
+
datePublished: string;
|
|
104
|
+
dateModified?: string;
|
|
105
|
+
image?: string;
|
|
106
|
+
authorName?: string;
|
|
107
|
+
publisherName: string;
|
|
108
|
+
publisherLogo?: string;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function blogPosting(input: BlogPostingInput) {
|
|
112
|
+
return {
|
|
113
|
+
'@context': 'https://schema.org',
|
|
114
|
+
'@type': 'BlogPosting',
|
|
115
|
+
headline: input.title,
|
|
116
|
+
description: input.description,
|
|
117
|
+
mainEntityOfPage: { '@type': 'WebPage', '@id': input.url },
|
|
118
|
+
url: input.url,
|
|
119
|
+
datePublished: input.datePublished,
|
|
120
|
+
dateModified: input.dateModified ?? input.datePublished,
|
|
121
|
+
...(input.image && { image: input.image }),
|
|
122
|
+
...(input.authorName && {
|
|
123
|
+
author: { '@type': 'Person', name: input.authorName },
|
|
124
|
+
}),
|
|
125
|
+
publisher: {
|
|
126
|
+
'@type': 'Organization',
|
|
127
|
+
name: input.publisherName,
|
|
128
|
+
...(input.publisherLogo && {
|
|
129
|
+
logo: { '@type': 'ImageObject', url: input.publisherLogo },
|
|
130
|
+
}),
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export interface FAQ {
|
|
136
|
+
question: string;
|
|
137
|
+
answer: string;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function faqPage(faqs: FAQ[]) {
|
|
141
|
+
return {
|
|
142
|
+
'@context': 'https://schema.org',
|
|
143
|
+
'@type': 'FAQPage',
|
|
144
|
+
mainEntity: faqs.map((faq) => ({
|
|
145
|
+
'@type': 'Question',
|
|
146
|
+
name: faq.question,
|
|
147
|
+
acceptedAnswer: {
|
|
148
|
+
'@type': 'Answer',
|
|
149
|
+
text: faq.answer,
|
|
150
|
+
},
|
|
151
|
+
})),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export interface BreadcrumbItem {
|
|
156
|
+
name: string;
|
|
157
|
+
url: string;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function breadcrumbList(items: BreadcrumbItem[]) {
|
|
161
|
+
return {
|
|
162
|
+
'@context': 'https://schema.org',
|
|
163
|
+
'@type': 'BreadcrumbList',
|
|
164
|
+
itemListElement: items.map((item, i) => ({
|
|
165
|
+
'@type': 'ListItem',
|
|
166
|
+
position: i + 1,
|
|
167
|
+
name: item.name,
|
|
168
|
+
item: item.url,
|
|
169
|
+
})),
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export interface ServiceInput {
|
|
174
|
+
name: string;
|
|
175
|
+
description: string;
|
|
176
|
+
url: string;
|
|
177
|
+
provider: { name: string; url: string };
|
|
178
|
+
areaServed?: string[];
|
|
179
|
+
serviceType?: string;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function service(input: ServiceInput) {
|
|
183
|
+
return {
|
|
184
|
+
'@context': 'https://schema.org',
|
|
185
|
+
'@type': 'Service',
|
|
186
|
+
name: input.name,
|
|
187
|
+
description: input.description,
|
|
188
|
+
url: input.url,
|
|
189
|
+
provider: {
|
|
190
|
+
'@type': 'Organization',
|
|
191
|
+
name: input.provider.name,
|
|
192
|
+
url: input.provider.url,
|
|
193
|
+
},
|
|
194
|
+
...(input.areaServed && { areaServed: input.areaServed }),
|
|
195
|
+
...(input.serviceType && { serviceType: input.serviceType }),
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** Stringify a schema object for safe injection into <script type="application/ld+json">. */
|
|
200
|
+
export function jsonLd(schema: unknown): string {
|
|
201
|
+
// Escape `</` to prevent breakout from the script tag.
|
|
202
|
+
return JSON.stringify(schema).replace(/<\//g, '<\\/');
|
|
203
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers for the `@astrojs/sitemap` integration.
|
|
3
|
+
*
|
|
4
|
+
* Two complementary functions:
|
|
5
|
+
* - `defaultPriorityRules(site)` produces a `serialize` function that sets
|
|
6
|
+
* priority and changefreq based on URL shape (home, top-level hubs,
|
|
7
|
+
* service/area pages, blog posts).
|
|
8
|
+
* - `excludeFuturePosts(futureSlugs)` produces a `filter` function that
|
|
9
|
+
* drops scheduled blog posts whose slugs are in the supplied set.
|
|
10
|
+
*
|
|
11
|
+
* Compose both like:
|
|
12
|
+
*
|
|
13
|
+
* sitemap({
|
|
14
|
+
* filter: excludeFuturePosts(await blog.getFutureBlogSlugs()),
|
|
15
|
+
* serialize: defaultPriorityRules(SITE),
|
|
16
|
+
* })
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export interface SitemapItem {
|
|
20
|
+
url: string;
|
|
21
|
+
changefreq?:
|
|
22
|
+
| 'always'
|
|
23
|
+
| 'hourly'
|
|
24
|
+
| 'daily'
|
|
25
|
+
| 'weekly'
|
|
26
|
+
| 'monthly'
|
|
27
|
+
| 'yearly'
|
|
28
|
+
| 'never';
|
|
29
|
+
priority?: number;
|
|
30
|
+
lastmod?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface PriorityRulesOptions {
|
|
34
|
+
/** Extra patterns appended to the defaults. Earlier entries win. */
|
|
35
|
+
rules?: Array<{ pattern: RegExp; priority: number; changefreq?: SitemapItem['changefreq'] }>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Returns a `serialize` function that assigns priority/changefreq based on
|
|
40
|
+
* URL shape. Tune via `options.rules` for site-specific top-level paths.
|
|
41
|
+
*
|
|
42
|
+
* @param site The site origin (no trailing slash), e.g. 'https://example.com'.
|
|
43
|
+
*/
|
|
44
|
+
export function defaultPriorityRules(
|
|
45
|
+
site: string,
|
|
46
|
+
options: PriorityRulesOptions = {}
|
|
47
|
+
) {
|
|
48
|
+
const homeUrl = `${site}/`;
|
|
49
|
+
const extra = options.rules ?? [];
|
|
50
|
+
|
|
51
|
+
return (item: SitemapItem): SitemapItem => {
|
|
52
|
+
if (item.url === homeUrl) {
|
|
53
|
+
return { ...item, priority: 1.0, changefreq: 'daily' };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
for (const rule of extra) {
|
|
57
|
+
if (rule.pattern.test(item.url)) {
|
|
58
|
+
return {
|
|
59
|
+
...item,
|
|
60
|
+
priority: rule.priority,
|
|
61
|
+
...(rule.changefreq && { changefreq: rule.changefreq }),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Default heuristics.
|
|
67
|
+
if (/\/(services|service-areas|industries)\/$/.test(item.url)) {
|
|
68
|
+
return { ...item, priority: 0.9, changefreq: 'weekly' };
|
|
69
|
+
}
|
|
70
|
+
if (/\/(services|service-areas|industries)\/[^/]+\/$/.test(item.url)) {
|
|
71
|
+
return { ...item, priority: 0.8, changefreq: 'weekly' };
|
|
72
|
+
}
|
|
73
|
+
if (/\/blog\/$/.test(item.url)) {
|
|
74
|
+
return { ...item, priority: 0.7, changefreq: 'daily' };
|
|
75
|
+
}
|
|
76
|
+
if (/\/blog\/[^/]+\/$/.test(item.url)) {
|
|
77
|
+
return { ...item, priority: 0.6, changefreq: 'monthly' };
|
|
78
|
+
}
|
|
79
|
+
if (/\/blog\/(tag|category)\/[^/]+\/$/.test(item.url)) {
|
|
80
|
+
return { ...item, priority: 0.5, changefreq: 'weekly' };
|
|
81
|
+
}
|
|
82
|
+
return item;
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Returns a `filter` function for `@astrojs/sitemap` that excludes any URL
|
|
88
|
+
* whose final path segment matches a future-dated blog slug.
|
|
89
|
+
*
|
|
90
|
+
* Pass `await blog.getFutureBlogSlugs()` to the constructor.
|
|
91
|
+
*/
|
|
92
|
+
export function excludeFuturePosts(futureSlugs: string[]) {
|
|
93
|
+
const set = new Set(futureSlugs);
|
|
94
|
+
return (url: string): boolean => {
|
|
95
|
+
const m = url.match(/\/blog\/([^/]+)\/?$/);
|
|
96
|
+
if (!m) return true;
|
|
97
|
+
return !set.has(m[1]);
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Compose multiple filter functions with AND semantics. */
|
|
102
|
+
export function composeFilters(...filters: Array<(url: string) => boolean>) {
|
|
103
|
+
return (url: string) => filters.every((f) => f(url));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Filter helper: exclude URLs matching any of the supplied patterns. */
|
|
107
|
+
export function excludePatterns(patterns: RegExp[]) {
|
|
108
|
+
return (url: string) => !patterns.some((p) => p.test(url));
|
|
109
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { jsonLd } from '@ibalzam/codejitsu-core/seo/schema';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
title: string;
|
|
6
|
+
description: string;
|
|
7
|
+
/** Absolute URL (with trailing slash). Defaults to current page. */
|
|
8
|
+
canonical?: string;
|
|
9
|
+
/** Absolute URL to OG image. */
|
|
10
|
+
ogImage?: string;
|
|
11
|
+
/** og:type. Defaults to 'website'. */
|
|
12
|
+
ogType?: 'website' | 'article';
|
|
13
|
+
/** JSON-LD schema objects to inject. Use builders from @ibalzam/codejitsu-core/seo/schema. */
|
|
14
|
+
schema?: unknown[];
|
|
15
|
+
/** Override <html lang>. Defaults to 'en'. */
|
|
16
|
+
noindex?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const {
|
|
20
|
+
title,
|
|
21
|
+
description,
|
|
22
|
+
canonical,
|
|
23
|
+
ogImage,
|
|
24
|
+
ogType = 'website',
|
|
25
|
+
schema = [],
|
|
26
|
+
noindex = false,
|
|
27
|
+
} = Astro.props;
|
|
28
|
+
|
|
29
|
+
const canonicalUrl = canonical ?? new URL(Astro.url.pathname, Astro.site).toString();
|
|
30
|
+
const absOgImage = ogImage
|
|
31
|
+
? new URL(ogImage, Astro.site).toString()
|
|
32
|
+
: undefined;
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
<title>{title}</title>
|
|
36
|
+
<meta name="description" content={description} />
|
|
37
|
+
<link rel="canonical" href={canonicalUrl} />
|
|
38
|
+
{noindex && <meta name="robots" content="noindex,follow" />}
|
|
39
|
+
|
|
40
|
+
<meta property="og:title" content={title} />
|
|
41
|
+
<meta property="og:description" content={description} />
|
|
42
|
+
<meta property="og:url" content={canonicalUrl} />
|
|
43
|
+
<meta property="og:type" content={ogType} />
|
|
44
|
+
{absOgImage && <meta property="og:image" content={absOgImage} />}
|
|
45
|
+
|
|
46
|
+
<meta name="twitter:card" content="summary_large_image" />
|
|
47
|
+
<meta name="twitter:title" content={title} />
|
|
48
|
+
<meta name="twitter:description" content={description} />
|
|
49
|
+
{absOgImage && <meta name="twitter:image" content={absOgImage} />}
|
|
50
|
+
|
|
51
|
+
{schema.map((item) => (
|
|
52
|
+
<script type="application/ld+json" set:html={jsonLd(item)} />
|
|
53
|
+
))}
|
package/package.json
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ibalzam/codejitsu-core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Shared core for Codejitsu Astro sites — reusable code and Claude-facing instructions for blog, SEO, images, deploy, and llms.txt.",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"astro",
|
|
8
|
+
"blog",
|
|
9
|
+
"seo",
|
|
10
|
+
"schema.org",
|
|
11
|
+
"sitemap",
|
|
12
|
+
"llms.txt",
|
|
13
|
+
"cloudflare-pages",
|
|
14
|
+
"webp",
|
|
15
|
+
"claude"
|
|
16
|
+
],
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"author": "Ika Balzam <ika@codejitsu.ca>",
|
|
19
|
+
"homepage": "https://github.com/ibalzam/codejitsu-site-kit#readme",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/ibalzam/codejitsu-site-kit.git"
|
|
23
|
+
},
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/ibalzam/codejitsu-site-kit/issues"
|
|
26
|
+
},
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=20"
|
|
29
|
+
},
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public"
|
|
32
|
+
},
|
|
33
|
+
"exports": {
|
|
34
|
+
".": "./src/index.ts",
|
|
35
|
+
"./blog": "./modules/blog/src/index.ts",
|
|
36
|
+
"./blog/components": "./modules/blog/src/components/index.ts",
|
|
37
|
+
"./seo": "./modules/seo/src/index.ts",
|
|
38
|
+
"./seo/schema": "./modules/seo/src/schema.ts",
|
|
39
|
+
"./seo/sitemap": "./modules/seo/src/sitemap.ts",
|
|
40
|
+
"./images": "./modules/images/src/index.ts",
|
|
41
|
+
"./package.json": "./package.json"
|
|
42
|
+
},
|
|
43
|
+
"bin": {
|
|
44
|
+
"codejitsu-llms": "./modules/llms/bin/generate.mjs",
|
|
45
|
+
"codejitsu-optimize-images": "./modules/images/bin/optimize.mjs",
|
|
46
|
+
"codejitsu-check": "./checklist/bin/run.mjs"
|
|
47
|
+
},
|
|
48
|
+
"files": [
|
|
49
|
+
"src",
|
|
50
|
+
"modules",
|
|
51
|
+
"checklist",
|
|
52
|
+
"MIGRATIONS",
|
|
53
|
+
"CLAUDE.md",
|
|
54
|
+
"README.md",
|
|
55
|
+
"LICENSE"
|
|
56
|
+
],
|
|
57
|
+
"scripts": {
|
|
58
|
+
"typecheck": "tsc --noEmit",
|
|
59
|
+
"prepublishOnly": "npm run typecheck"
|
|
60
|
+
},
|
|
61
|
+
"peerDependencies": {
|
|
62
|
+
"astro": ">=5.0.0"
|
|
63
|
+
},
|
|
64
|
+
"dependencies": {
|
|
65
|
+
"gray-matter": "^4.0.3",
|
|
66
|
+
"reading-time": "^1.5.0",
|
|
67
|
+
"sharp": "^0.33.5"
|
|
68
|
+
},
|
|
69
|
+
"devDependencies": {
|
|
70
|
+
"@types/node": "^20.11.0",
|
|
71
|
+
"typescript": "^5.4.0"
|
|
72
|
+
}
|
|
73
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const CORE_VERSION = '0.1.0';
|