create-atsdc-stack 1.0.1 → 1.2.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/settings.local.json +3 -1
- package/CLAUDE.md +236 -0
- package/CONTRIBUTING.md +342 -342
- package/INSTALLATION.md +359 -359
- package/LICENSE +201 -201
- package/README.md +405 -405
- package/app/.env.example +17 -17
- package/app/.github/labeler.yml +61 -0
- package/app/.github/workflows/browser-tests.yml +101 -0
- package/app/.github/workflows/check.yml +24 -0
- package/app/.github/workflows/greetings.yml +16 -0
- package/app/.github/workflows/label.yml +22 -0
- package/app/.github/workflows/stale.yml +27 -0
- package/app/.github/workflows/summary.yml +34 -0
- package/app/.stylelintrc.json +8 -0
- package/app/README.md +251 -251
- package/app/astro.config.mjs +83 -83
- package/app/drizzle.config.ts +16 -16
- package/app/package.json +66 -52
- package/app/playwright.config.ts +27 -0
- package/app/public/manifest.webmanifest +36 -36
- package/app/pwa-assets.config.ts +8 -0
- package/app/src/components/Card.astro +36 -36
- package/app/src/db/initialize.ts +107 -107
- package/app/src/db/schema.ts +72 -72
- package/app/src/db/validations.ts +158 -158
- package/app/src/layouts/Layout.astro +63 -63
- package/app/src/lib/config.ts +36 -36
- package/app/src/lib/content-converter.ts +141 -141
- package/app/src/lib/dom-utils.ts +230 -230
- package/app/src/lib/exa-search.ts +269 -269
- package/app/src/pages/api/chat.ts +91 -91
- package/app/src/pages/api/posts.ts +350 -350
- package/app/src/pages/index.astro +87 -87
- package/app/src/styles/components/button.scss +152 -152
- package/app/src/styles/components/card.scss +180 -180
- package/app/src/styles/components/form.scss +240 -240
- package/app/src/styles/global.scss +141 -141
- package/app/src/styles/pages/index.scss +80 -80
- package/app/src/styles/reset.scss +83 -83
- package/app/src/styles/variables/globals.scss +96 -96
- package/app/src/styles/variables/mixins.scss +238 -238
- package/app/tests/browser.test.nopause.ts +10 -0
- package/app/tests/browser.test.ts +13 -0
- package/bin/cli.js +1151 -1138
- package/package.json +8 -6
- package/app/.astro/settings.json +0 -5
- package/app/.astro/types.d.ts +0 -1
package/app/src/lib/dom-utils.ts
CHANGED
|
@@ -1,230 +1,230 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* DOM Manipulation Utilities
|
|
3
|
-
* Provides functions for manipulating HTML content using Cheerio
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import * as cheerio from 'cheerio';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Extract metadata from HTML content
|
|
10
|
-
* @param html - The HTML string to parse
|
|
11
|
-
* @returns Metadata object
|
|
12
|
-
*/
|
|
13
|
-
export function extractMetadata(html: string) {
|
|
14
|
-
const $ = cheerio.load(html);
|
|
15
|
-
|
|
16
|
-
return {
|
|
17
|
-
title: $('title').text() || $('h1').first().text() || '',
|
|
18
|
-
description: $('meta[name="description"]').attr('content') || '',
|
|
19
|
-
keywords: $('meta[name="keywords"]').attr('content') || '',
|
|
20
|
-
ogTitle: $('meta[property="og:title"]').attr('content') || '',
|
|
21
|
-
ogDescription: $('meta[property="og:description"]').attr('content') || '',
|
|
22
|
-
ogImage: $('meta[property="og:image"]').attr('content') || '',
|
|
23
|
-
twitterCard: $('meta[name="twitter:card"]').attr('content') || '',
|
|
24
|
-
canonical: $('link[rel="canonical"]').attr('href') || '',
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Extract all links from HTML
|
|
30
|
-
* @param html - The HTML string to parse
|
|
31
|
-
* @returns Array of link objects
|
|
32
|
-
*/
|
|
33
|
-
export function extractLinks(html: string) {
|
|
34
|
-
const $ = cheerio.load(html);
|
|
35
|
-
const links: Array<{ text: string; href: string; title?: string }> = [];
|
|
36
|
-
|
|
37
|
-
$('a').each((_, element) => {
|
|
38
|
-
const $el = $(element);
|
|
39
|
-
const href = $el.attr('href');
|
|
40
|
-
if (href) {
|
|
41
|
-
links.push({
|
|
42
|
-
text: $el.text().trim(),
|
|
43
|
-
href,
|
|
44
|
-
title: $el.attr('title'),
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
return links;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Extract all images from HTML
|
|
54
|
-
* @param html - The HTML string to parse
|
|
55
|
-
* @returns Array of image objects
|
|
56
|
-
*/
|
|
57
|
-
export function extractImages(html: string) {
|
|
58
|
-
const $ = cheerio.load(html);
|
|
59
|
-
const images: Array<{ src: string; alt?: string; title?: string }> = [];
|
|
60
|
-
|
|
61
|
-
$('img').each((_, element) => {
|
|
62
|
-
const $el = $(element);
|
|
63
|
-
const src = $el.attr('src');
|
|
64
|
-
if (src) {
|
|
65
|
-
images.push({
|
|
66
|
-
src,
|
|
67
|
-
alt: $el.attr('alt'),
|
|
68
|
-
title: $el.attr('title'),
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
return images;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Extract headings from HTML
|
|
78
|
-
* @param html - The HTML string to parse
|
|
79
|
-
* @returns Array of heading objects
|
|
80
|
-
*/
|
|
81
|
-
export function extractHeadings(html: string) {
|
|
82
|
-
const $ = cheerio.load(html);
|
|
83
|
-
const headings: Array<{ level: number; text: string; id?: string }> = [];
|
|
84
|
-
|
|
85
|
-
$('h1, h2, h3, h4, h5, h6').each((_, element) => {
|
|
86
|
-
const $el = $(element);
|
|
87
|
-
const tagName = $el.prop('tagName')?.toLowerCase();
|
|
88
|
-
const level = parseInt(tagName?.replace('h', '') || '1');
|
|
89
|
-
|
|
90
|
-
headings.push({
|
|
91
|
-
level,
|
|
92
|
-
text: $el.text().trim(),
|
|
93
|
-
id: $el.attr('id'),
|
|
94
|
-
});
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
return headings;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Generate table of contents from HTML
|
|
102
|
-
* @param html - The HTML string to parse
|
|
103
|
-
* @param maxLevel - Maximum heading level to include (default: 3)
|
|
104
|
-
* @returns Array of TOC items
|
|
105
|
-
*/
|
|
106
|
-
export function generateTableOfContents(html: string, maxLevel: number = 3) {
|
|
107
|
-
const headings = extractHeadings(html);
|
|
108
|
-
return headings
|
|
109
|
-
.filter((h) => h.level <= maxLevel)
|
|
110
|
-
.map((h) => ({
|
|
111
|
-
...h,
|
|
112
|
-
slug: h.id || h.text.toLowerCase().replace(/\s+/g, '-').replace(/[^\w-]/g, ''),
|
|
113
|
-
}));
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Clean HTML content
|
|
118
|
-
* Removes scripts, styles, and other potentially dangerous elements
|
|
119
|
-
* @param html - The HTML string to clean
|
|
120
|
-
* @returns Cleaned HTML string
|
|
121
|
-
*/
|
|
122
|
-
export function cleanHtml(html: string): string {
|
|
123
|
-
const $ = cheerio.load(html);
|
|
124
|
-
|
|
125
|
-
// Remove dangerous elements
|
|
126
|
-
$('script, style, iframe, object, embed').remove();
|
|
127
|
-
|
|
128
|
-
// Remove event handlers
|
|
129
|
-
$('*').each((_, element) => {
|
|
130
|
-
const $el = $(element);
|
|
131
|
-
const attrs = $el.attr();
|
|
132
|
-
|
|
133
|
-
if (attrs) {
|
|
134
|
-
Object.keys(attrs).forEach((attr) => {
|
|
135
|
-
if (attr.startsWith('on')) {
|
|
136
|
-
$el.removeAttr(attr);
|
|
137
|
-
}
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
return $.html();
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Extract text content from HTML
|
|
147
|
-
* @param html - The HTML string to parse
|
|
148
|
-
* @returns Plain text string
|
|
149
|
-
*/
|
|
150
|
-
export function extractTextContent(html: string): string {
|
|
151
|
-
const $ = cheerio.load(html);
|
|
152
|
-
|
|
153
|
-
// Remove script and style elements
|
|
154
|
-
$('script, style').remove();
|
|
155
|
-
|
|
156
|
-
return $('body').text().replace(/\s+/g, ' ').trim();
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Add IDs to headings for anchor linking
|
|
161
|
-
* @param html - The HTML string to process
|
|
162
|
-
* @returns HTML with IDs added to headings
|
|
163
|
-
*/
|
|
164
|
-
export function addHeadingIds(html: string): string {
|
|
165
|
-
const $ = cheerio.load(html);
|
|
166
|
-
|
|
167
|
-
$('h1, h2, h3, h4, h5, h6').each((_, element) => {
|
|
168
|
-
const $el = $(element);
|
|
169
|
-
|
|
170
|
-
if (!$el.attr('id')) {
|
|
171
|
-
const text = $el.text().trim();
|
|
172
|
-
const id = text.toLowerCase().replace(/\s+/g, '-').replace(/[^\w-]/g, '');
|
|
173
|
-
$el.attr('id', id);
|
|
174
|
-
}
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
return $.html();
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Add target="_blank" to external links
|
|
182
|
-
* @param html - The HTML string to process
|
|
183
|
-
* @param domain - The current domain (optional)
|
|
184
|
-
* @returns HTML with external links updated
|
|
185
|
-
*/
|
|
186
|
-
export function addExternalLinkTargets(html: string, domain?: string): string {
|
|
187
|
-
const $ = cheerio.load(html);
|
|
188
|
-
|
|
189
|
-
$('a').each((_, element) => {
|
|
190
|
-
const $el = $(element);
|
|
191
|
-
const href = $el.attr('href');
|
|
192
|
-
|
|
193
|
-
if (href && (href.startsWith('http://') || href.startsWith('https://'))) {
|
|
194
|
-
if (!domain || !href.includes(domain)) {
|
|
195
|
-
$el.attr('target', '_blank');
|
|
196
|
-
$el.attr('rel', 'noopener noreferrer');
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
return $.html();
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Calculate reading time for HTML content
|
|
206
|
-
* @param html - The HTML string to analyze
|
|
207
|
-
* @param wordsPerMinute - Average reading speed (default: 200)
|
|
208
|
-
* @returns Reading time in minutes
|
|
209
|
-
*/
|
|
210
|
-
export function calculateReadingTime(html: string, wordsPerMinute: number = 200): number {
|
|
211
|
-
const text = extractTextContent(html);
|
|
212
|
-
const wordCount = text.split(/\s+/).length;
|
|
213
|
-
return Math.ceil(wordCount / wordsPerMinute);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* Wrap tables in a responsive container
|
|
218
|
-
* @param html - The HTML string to process
|
|
219
|
-
* @returns HTML with tables wrapped
|
|
220
|
-
*/
|
|
221
|
-
export function wrapTables(html: string): string {
|
|
222
|
-
const $ = cheerio.load(html);
|
|
223
|
-
|
|
224
|
-
$('table').each((_, element) => {
|
|
225
|
-
const $el = $(element);
|
|
226
|
-
$el.wrap('<div class="table-wrapper" style="overflow-x: auto;"></div>');
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
return $.html();
|
|
230
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* DOM Manipulation Utilities
|
|
3
|
+
* Provides functions for manipulating HTML content using Cheerio
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as cheerio from 'cheerio';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Extract metadata from HTML content
|
|
10
|
+
* @param html - The HTML string to parse
|
|
11
|
+
* @returns Metadata object
|
|
12
|
+
*/
|
|
13
|
+
export function extractMetadata(html: string) {
|
|
14
|
+
const $ = cheerio.load(html);
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
title: $('title').text() || $('h1').first().text() || '',
|
|
18
|
+
description: $('meta[name="description"]').attr('content') || '',
|
|
19
|
+
keywords: $('meta[name="keywords"]').attr('content') || '',
|
|
20
|
+
ogTitle: $('meta[property="og:title"]').attr('content') || '',
|
|
21
|
+
ogDescription: $('meta[property="og:description"]').attr('content') || '',
|
|
22
|
+
ogImage: $('meta[property="og:image"]').attr('content') || '',
|
|
23
|
+
twitterCard: $('meta[name="twitter:card"]').attr('content') || '',
|
|
24
|
+
canonical: $('link[rel="canonical"]').attr('href') || '',
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Extract all links from HTML
|
|
30
|
+
* @param html - The HTML string to parse
|
|
31
|
+
* @returns Array of link objects
|
|
32
|
+
*/
|
|
33
|
+
export function extractLinks(html: string) {
|
|
34
|
+
const $ = cheerio.load(html);
|
|
35
|
+
const links: Array<{ text: string; href: string; title?: string }> = [];
|
|
36
|
+
|
|
37
|
+
$('a').each((_, element) => {
|
|
38
|
+
const $el = $(element);
|
|
39
|
+
const href = $el.attr('href');
|
|
40
|
+
if (href) {
|
|
41
|
+
links.push({
|
|
42
|
+
text: $el.text().trim(),
|
|
43
|
+
href,
|
|
44
|
+
title: $el.attr('title'),
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
return links;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Extract all images from HTML
|
|
54
|
+
* @param html - The HTML string to parse
|
|
55
|
+
* @returns Array of image objects
|
|
56
|
+
*/
|
|
57
|
+
export function extractImages(html: string) {
|
|
58
|
+
const $ = cheerio.load(html);
|
|
59
|
+
const images: Array<{ src: string; alt?: string; title?: string }> = [];
|
|
60
|
+
|
|
61
|
+
$('img').each((_, element) => {
|
|
62
|
+
const $el = $(element);
|
|
63
|
+
const src = $el.attr('src');
|
|
64
|
+
if (src) {
|
|
65
|
+
images.push({
|
|
66
|
+
src,
|
|
67
|
+
alt: $el.attr('alt'),
|
|
68
|
+
title: $el.attr('title'),
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
return images;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Extract headings from HTML
|
|
78
|
+
* @param html - The HTML string to parse
|
|
79
|
+
* @returns Array of heading objects
|
|
80
|
+
*/
|
|
81
|
+
export function extractHeadings(html: string) {
|
|
82
|
+
const $ = cheerio.load(html);
|
|
83
|
+
const headings: Array<{ level: number; text: string; id?: string }> = [];
|
|
84
|
+
|
|
85
|
+
$('h1, h2, h3, h4, h5, h6').each((_, element) => {
|
|
86
|
+
const $el = $(element);
|
|
87
|
+
const tagName = $el.prop('tagName')?.toLowerCase();
|
|
88
|
+
const level = parseInt(tagName?.replace('h', '') || '1');
|
|
89
|
+
|
|
90
|
+
headings.push({
|
|
91
|
+
level,
|
|
92
|
+
text: $el.text().trim(),
|
|
93
|
+
id: $el.attr('id'),
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
return headings;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Generate table of contents from HTML
|
|
102
|
+
* @param html - The HTML string to parse
|
|
103
|
+
* @param maxLevel - Maximum heading level to include (default: 3)
|
|
104
|
+
* @returns Array of TOC items
|
|
105
|
+
*/
|
|
106
|
+
export function generateTableOfContents(html: string, maxLevel: number = 3) {
|
|
107
|
+
const headings = extractHeadings(html);
|
|
108
|
+
return headings
|
|
109
|
+
.filter((h) => h.level <= maxLevel)
|
|
110
|
+
.map((h) => ({
|
|
111
|
+
...h,
|
|
112
|
+
slug: h.id || h.text.toLowerCase().replace(/\s+/g, '-').replace(/[^\w-]/g, ''),
|
|
113
|
+
}));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Clean HTML content
|
|
118
|
+
* Removes scripts, styles, and other potentially dangerous elements
|
|
119
|
+
* @param html - The HTML string to clean
|
|
120
|
+
* @returns Cleaned HTML string
|
|
121
|
+
*/
|
|
122
|
+
export function cleanHtml(html: string): string {
|
|
123
|
+
const $ = cheerio.load(html);
|
|
124
|
+
|
|
125
|
+
// Remove dangerous elements
|
|
126
|
+
$('script, style, iframe, object, embed').remove();
|
|
127
|
+
|
|
128
|
+
// Remove event handlers
|
|
129
|
+
$('*').each((_, element) => {
|
|
130
|
+
const $el = $(element);
|
|
131
|
+
const attrs = $el.attr();
|
|
132
|
+
|
|
133
|
+
if (attrs) {
|
|
134
|
+
Object.keys(attrs).forEach((attr) => {
|
|
135
|
+
if (attr.startsWith('on')) {
|
|
136
|
+
$el.removeAttr(attr);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
return $.html();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Extract text content from HTML
|
|
147
|
+
* @param html - The HTML string to parse
|
|
148
|
+
* @returns Plain text string
|
|
149
|
+
*/
|
|
150
|
+
export function extractTextContent(html: string): string {
|
|
151
|
+
const $ = cheerio.load(html);
|
|
152
|
+
|
|
153
|
+
// Remove script and style elements
|
|
154
|
+
$('script, style').remove();
|
|
155
|
+
|
|
156
|
+
return $('body').text().replace(/\s+/g, ' ').trim();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Add IDs to headings for anchor linking
|
|
161
|
+
* @param html - The HTML string to process
|
|
162
|
+
* @returns HTML with IDs added to headings
|
|
163
|
+
*/
|
|
164
|
+
export function addHeadingIds(html: string): string {
|
|
165
|
+
const $ = cheerio.load(html);
|
|
166
|
+
|
|
167
|
+
$('h1, h2, h3, h4, h5, h6').each((_, element) => {
|
|
168
|
+
const $el = $(element);
|
|
169
|
+
|
|
170
|
+
if (!$el.attr('id')) {
|
|
171
|
+
const text = $el.text().trim();
|
|
172
|
+
const id = text.toLowerCase().replace(/\s+/g, '-').replace(/[^\w-]/g, '');
|
|
173
|
+
$el.attr('id', id);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
return $.html();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Add target="_blank" to external links
|
|
182
|
+
* @param html - The HTML string to process
|
|
183
|
+
* @param domain - The current domain (optional)
|
|
184
|
+
* @returns HTML with external links updated
|
|
185
|
+
*/
|
|
186
|
+
export function addExternalLinkTargets(html: string, domain?: string): string {
|
|
187
|
+
const $ = cheerio.load(html);
|
|
188
|
+
|
|
189
|
+
$('a').each((_, element) => {
|
|
190
|
+
const $el = $(element);
|
|
191
|
+
const href = $el.attr('href');
|
|
192
|
+
|
|
193
|
+
if (href && (href.startsWith('http://') || href.startsWith('https://'))) {
|
|
194
|
+
if (!domain || !href.includes(domain)) {
|
|
195
|
+
$el.attr('target', '_blank');
|
|
196
|
+
$el.attr('rel', 'noopener noreferrer');
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
return $.html();
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Calculate reading time for HTML content
|
|
206
|
+
* @param html - The HTML string to analyze
|
|
207
|
+
* @param wordsPerMinute - Average reading speed (default: 200)
|
|
208
|
+
* @returns Reading time in minutes
|
|
209
|
+
*/
|
|
210
|
+
export function calculateReadingTime(html: string, wordsPerMinute: number = 200): number {
|
|
211
|
+
const text = extractTextContent(html);
|
|
212
|
+
const wordCount = text.split(/\s+/).length;
|
|
213
|
+
return Math.ceil(wordCount / wordsPerMinute);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Wrap tables in a responsive container
|
|
218
|
+
* @param html - The HTML string to process
|
|
219
|
+
* @returns HTML with tables wrapped
|
|
220
|
+
*/
|
|
221
|
+
export function wrapTables(html: string): string {
|
|
222
|
+
const $ = cheerio.load(html);
|
|
223
|
+
|
|
224
|
+
$('table').each((_, element) => {
|
|
225
|
+
const $el = $(element);
|
|
226
|
+
$el.wrap('<div class="table-wrapper" style="overflow-x: auto;"></div>');
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
return $.html();
|
|
230
|
+
}
|