@snappy-stack/sdk 0.1.5 ā 0.1.6
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/LICENSE +12 -12
- package/README.md +65 -65
- package/dist/bin/check.cjs +79 -0
- package/dist/bin/check.cjs.map +1 -0
- package/dist/bin/check.d.cts +1 -0
- package/dist/bin/check.d.ts +1 -0
- package/dist/bin/check.js +72 -0
- package/dist/bin/check.js.map +1 -0
- package/dist/chunk-TLDZ32ME.js +356 -0
- package/dist/chunk-TLDZ32ME.js.map +1 -0
- package/dist/chunk-X7RFIMX7.cjs +361 -0
- package/dist/chunk-X7RFIMX7.cjs.map +1 -0
- package/dist/client-FIWQgwNq.d.cts +325 -0
- package/dist/client-FIWQgwNq.d.ts +325 -0
- package/dist/index.cjs +85 -7
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +5 -38
- package/dist/index.d.ts +5 -38
- package/dist/index.js +67 -7
- package/dist/index.js.map +1 -0
- package/dist/plugin.cjs +209 -0
- package/dist/plugin.cjs.map +1 -0
- package/dist/plugin.d.cts +9 -0
- package/dist/plugin.d.ts +9 -0
- package/dist/plugin.js +198 -0
- package/dist/plugin.js.map +1 -0
- package/package.json +26 -7
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
export { createPublicClient } from './chunk-TLDZ32ME.js';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path2 from 'path';
|
|
4
|
+
|
|
5
|
+
function scanSource(srcDir) {
|
|
6
|
+
const files = getAllFiles(srcDir);
|
|
7
|
+
const result = {
|
|
8
|
+
hardcodedValues: 0,
|
|
9
|
+
missingAltTags: 0,
|
|
10
|
+
missingMetaTags: { title: false, description: false, ogImage: false },
|
|
11
|
+
unoptimizedImages: 0,
|
|
12
|
+
totalBuildSize: 0
|
|
13
|
+
};
|
|
14
|
+
const hexRegex = /#(?:[0-9a-fA-F]{3}){1,2}\b/g;
|
|
15
|
+
const rgbRegex = /rgb\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*\)/g;
|
|
16
|
+
const hslRegex = /hsl\(\s*\d+\s*,\s*\d+%\s*,\s*\d+%\s*\)/g;
|
|
17
|
+
const imgTagRegex = /<img\s+[^>]*>/gi;
|
|
18
|
+
const altAttrRegex = /alt=(?:["']|\{)/i;
|
|
19
|
+
for (const file of files) {
|
|
20
|
+
const content = fs.readFileSync(file, "utf8");
|
|
21
|
+
const ext = path2.extname(file);
|
|
22
|
+
const isStudio = file.replace(/\\/g, "/").includes("components/studio");
|
|
23
|
+
if (!isStudio && [".tsx", ".jsx", ".css", ".astro"].includes(ext)) {
|
|
24
|
+
let scanContent = content.replace(/:root\s*{[^}]*}/g, "").replace(/@theme\s*{[^}]*}/g, "");
|
|
25
|
+
const lines = scanContent.split("\n");
|
|
26
|
+
const filteredLines = lines.filter((line) => !line.trim().startsWith("--"));
|
|
27
|
+
scanContent = filteredLines.join("\n");
|
|
28
|
+
const hexMatches = scanContent.match(hexRegex) || [];
|
|
29
|
+
const rgbMatches = scanContent.match(rgbRegex) || [];
|
|
30
|
+
const hslMatches = scanContent.match(hslRegex) || [];
|
|
31
|
+
result.hardcodedValues += hexMatches.length + rgbMatches.length + hslMatches.length;
|
|
32
|
+
}
|
|
33
|
+
if ([".tsx", ".jsx", ".astro"].includes(ext)) {
|
|
34
|
+
const imgTags = content.match(imgTagRegex) || [];
|
|
35
|
+
for (const tag of imgTags) {
|
|
36
|
+
if (!altAttrRegex.test(tag)) {
|
|
37
|
+
result.missingAltTags++;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (file.toLowerCase().includes("layout") || file.toLowerCase().includes("index.astro")) {
|
|
42
|
+
if (content.includes("<title>") || content.includes("<SEO")) result.missingMetaTags.title = true;
|
|
43
|
+
if (content.includes('name="description"') || content.includes('property="description"') || content.includes("<SEO")) {
|
|
44
|
+
result.missingMetaTags.description = true;
|
|
45
|
+
}
|
|
46
|
+
if (content.includes('property="og:image"') || content.includes("<SEO")) result.missingMetaTags.ogImage = true;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
function getAllFiles(dirPath, arrayOfFiles = []) {
|
|
52
|
+
if (!fs.existsSync(dirPath)) return [];
|
|
53
|
+
const files = fs.readdirSync(dirPath);
|
|
54
|
+
files.forEach((file) => {
|
|
55
|
+
if (fs.statSync(path2.join(dirPath, file)).isDirectory()) {
|
|
56
|
+
if (file !== "node_modules" && file !== ".astro") {
|
|
57
|
+
arrayOfFiles = getAllFiles(path2.join(dirPath, file), arrayOfFiles);
|
|
58
|
+
}
|
|
59
|
+
} else {
|
|
60
|
+
arrayOfFiles.push(path2.join(dirPath, file));
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
return arrayOfFiles;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// src/audit/scorer.ts
|
|
67
|
+
function calculateScore(result) {
|
|
68
|
+
const weights = {
|
|
69
|
+
performance: 0.3,
|
|
70
|
+
accessibility: 0.2,
|
|
71
|
+
seo: 0.2,
|
|
72
|
+
compliance: 0.15,
|
|
73
|
+
optimization: 0.1,
|
|
74
|
+
bundle: 0.05
|
|
75
|
+
};
|
|
76
|
+
let perf = 100 - result.missingAltTags * 10;
|
|
77
|
+
perf = Math.max(0, perf);
|
|
78
|
+
let a11y = 100 - result.missingAltTags * 20;
|
|
79
|
+
a11y = Math.max(0, a11y);
|
|
80
|
+
let seo = 0;
|
|
81
|
+
if (result.missingMetaTags.title) seo += 40;
|
|
82
|
+
if (result.missingMetaTags.description) seo += 40;
|
|
83
|
+
if (result.missingMetaTags.ogImage) seo += 20;
|
|
84
|
+
let compliance = 100 - result.hardcodedValues * 5;
|
|
85
|
+
compliance = Math.max(0, compliance);
|
|
86
|
+
let optimization = 100;
|
|
87
|
+
let bundle = 100;
|
|
88
|
+
if (result.totalBuildSize > 10 * 1024 * 1024) bundle = 40;
|
|
89
|
+
const total = Math.round(
|
|
90
|
+
perf * weights.performance + a11y * weights.accessibility + seo * weights.seo + compliance * weights.compliance + optimization * weights.optimization + bundle * weights.bundle
|
|
91
|
+
);
|
|
92
|
+
return {
|
|
93
|
+
performance: perf,
|
|
94
|
+
accessibility: a11y,
|
|
95
|
+
seo,
|
|
96
|
+
compliance,
|
|
97
|
+
optimization,
|
|
98
|
+
bundle,
|
|
99
|
+
total
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// src/plugin.ts
|
|
104
|
+
function snappyCreditEnforcer() {
|
|
105
|
+
return {
|
|
106
|
+
name: "snappy-credit-enforcer",
|
|
107
|
+
async buildStart() {
|
|
108
|
+
const srcDir = path2.resolve(process.cwd(), "src");
|
|
109
|
+
const files = getAllFiles2(srcDir);
|
|
110
|
+
let hasCredit = false;
|
|
111
|
+
for (const file of files) {
|
|
112
|
+
if (file.endsWith(".astro") || file.endsWith(".tsx") || file.endsWith(".jsx")) {
|
|
113
|
+
const content = fs.readFileSync(file, "utf8");
|
|
114
|
+
if (content.includes("SnappyCredit") || content.includes("<SnappyCredit")) {
|
|
115
|
+
hasCredit = true;
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (!hasCredit) {
|
|
121
|
+
throw new Error(
|
|
122
|
+
"\n\n\x1B[31m[SNAPPY_SDK_E401]\x1B[0m\nBuild validation failed: Mandatory <SnappyCredit /> component missing.\n\n"
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
async buildEnd() {
|
|
127
|
+
const srcDir = path2.resolve(process.cwd(), "src");
|
|
128
|
+
const auditResult = scanSource(srcDir);
|
|
129
|
+
const score = calculateScore(auditResult);
|
|
130
|
+
printReport(score);
|
|
131
|
+
if (score.total < 50) {
|
|
132
|
+
console.error("\n\x1B[31m\u{1F479} BLOCKED:\x1B[0m SNAPPY Score is below the quality threshold (50).");
|
|
133
|
+
console.error("Ship clean or don't ship at all. \u{1F52B}\u{1F6E1}\uFE0F\u{1F493}\n");
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
const token = process.env.SNAPPY_TOKEN;
|
|
137
|
+
const baseUrl = process.env.SNAPPY_URL || "https://core.wicky.id";
|
|
138
|
+
if (token) {
|
|
139
|
+
try {
|
|
140
|
+
await fetch(`${baseUrl}/v1/projects/self/score`, {
|
|
141
|
+
method: "PATCH",
|
|
142
|
+
headers: {
|
|
143
|
+
"Authorization": `Bearer ${token}`,
|
|
144
|
+
"Content-Type": "application/json"
|
|
145
|
+
},
|
|
146
|
+
body: JSON.stringify({ score: score.total })
|
|
147
|
+
});
|
|
148
|
+
} catch (e) {
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
function printReport(s) {
|
|
155
|
+
const reset = "\x1B[0m";
|
|
156
|
+
const green = "\x1B[32m";
|
|
157
|
+
const yellow = "\x1B[33m";
|
|
158
|
+
const red = "\x1B[31m";
|
|
159
|
+
const cyan = "\x1B[36m";
|
|
160
|
+
const status = s.total >= 70 ? `${green}\u2705 PASS${reset}` : s.total >= 50 ? `${yellow}\u26A0\uFE0F WARNING${reset}` : `${red}\u{1F479} BLOCKED${reset}`;
|
|
161
|
+
console.log(`
|
|
162
|
+
${cyan}\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
|
|
163
|
+
\u2551 SNAPPY SCORE REPORT \u2551
|
|
164
|
+
\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563${reset}
|
|
165
|
+
\u2551 Performance ${fmt(s.performance)} 30% \u2551
|
|
166
|
+
\u2551 Accessibility ${fmt(s.accessibility)} 20% \u2551
|
|
167
|
+
\u2551 SEO Completeness ${fmt(s.seo)} 20% \u2551
|
|
168
|
+
\u2551 Zero Hardcoded ${fmt(s.compliance)} 15% \u2551
|
|
169
|
+
\u2551 Image Optimization ${fmt(s.optimization)} 10% \u2551
|
|
170
|
+
\u2551 Bundle Size ${fmt(s.bundle)} 5% \u2551
|
|
171
|
+
${cyan}\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563${reset}
|
|
172
|
+
\u2551 TOTAL SCORE: ${s.total}/100 \u2551
|
|
173
|
+
\u2551 STATUS: ${status} \u2551
|
|
174
|
+
${cyan}\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D${reset}
|
|
175
|
+
`);
|
|
176
|
+
}
|
|
177
|
+
function fmt(n) {
|
|
178
|
+
const str = `${n}/100`;
|
|
179
|
+
return str.padEnd(8, " ");
|
|
180
|
+
}
|
|
181
|
+
function getAllFiles2(dirPath, arrayOfFiles = []) {
|
|
182
|
+
if (!fs.existsSync(dirPath)) return [];
|
|
183
|
+
const files = fs.readdirSync(dirPath);
|
|
184
|
+
files.forEach((file) => {
|
|
185
|
+
if (fs.statSync(path2.join(dirPath, file)).isDirectory()) {
|
|
186
|
+
if (file !== "node_modules" && file !== ".astro") {
|
|
187
|
+
arrayOfFiles = getAllFiles2(path2.join(dirPath, file), arrayOfFiles);
|
|
188
|
+
}
|
|
189
|
+
} else {
|
|
190
|
+
arrayOfFiles.push(path2.join(dirPath, file));
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
return arrayOfFiles;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export { snappyCreditEnforcer };
|
|
197
|
+
//# sourceMappingURL=plugin.js.map
|
|
198
|
+
//# sourceMappingURL=plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/audit/scanner.ts","../src/audit/scorer.ts","../src/plugin.ts"],"names":["path","getAllFiles","fs"],"mappings":"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","file":"plugin.js","sourcesContent":["import fs from 'fs';\nimport path from 'path';\n\nexport interface AuditResult {\n hardcodedValues: number;\n missingAltTags: number;\n missingMetaTags: {\n title: boolean;\n description: boolean;\n ogImage: boolean;\n };\n unoptimizedImages: number;\n totalBuildSize: number; // in bytes\n}\n\nexport function scanSource(srcDir: string): AuditResult {\n const files = getAllFiles(srcDir);\n const result: AuditResult = {\n hardcodedValues: 0,\n missingAltTags: 0,\n missingMetaTags: { title: false, description: false, ogImage: false },\n unoptimizedImages: 0,\n totalBuildSize: 0\n };\n\n // Regex patterns\n const hexRegex = /#(?:[0-9a-fA-F]{3}){1,2}\\b/g;\n const rgbRegex = /rgb\\(\\s*\\d+\\s*,\\s*\\d+\\s*,\\s*\\d+\\s*\\)/g;\n const hslRegex = /hsl\\(\\s*\\d+\\s*,\\s*\\d+%\\s*,\\s*\\d+%\\s*\\)/g;\n const imgTagRegex = /<img\\s+[^>]*>/gi;\n const altAttrRegex = /alt=(?:[\"']|\\{)/i;\n\n for (const file of files) {\n const content = fs.readFileSync(file, 'utf8');\n const ext = path.extname(file);\n\n // 1. Style Scan (Hardcoded values)\n const isStudio = file.replace(/\\\\/g, '/').includes('components/studio');\n\n if (!isStudio && ['.tsx', '.jsx', '.css', '.astro'].includes(ext)) {\n // Smarter scan: ignore definitions in :root or @theme or lines defining --vars\n let scanContent = content\n .replace(/:root\\s*{[^}]*}/g, '')\n .replace(/@theme\\s*{[^}]*}/g, '');\n \n // Also ignore lines that are clearly CSS variable definitions\n const lines = scanContent.split('\\n');\n const filteredLines = lines.filter(line => !line.trim().startsWith('--'));\n scanContent = filteredLines.join('\\n');\n\n const hexMatches = scanContent.match(hexRegex) || [];\n const rgbMatches = scanContent.match(rgbRegex) || [];\n const hslMatches = scanContent.match(hslRegex) || [];\n result.hardcodedValues += hexMatches.length + rgbMatches.length + hslMatches.length;\n }\n\n // 2. Accessibility Scan (Alt tags)\n if (['.tsx', '.jsx', '.astro'].includes(ext)) {\n const imgTags = content.match(imgTagRegex) || [];\n for (const tag of imgTags) {\n if (!altAttrRegex.test(tag)) {\n result.missingAltTags++;\n }\n }\n }\n\n // 3. SEO Scan (Only in primary layouts or index)\n if (file.toLowerCase().includes('layout') || file.toLowerCase().includes('index.astro')) {\n if (content.includes('<title>') || content.includes('<SEO')) result.missingMetaTags.title = true;\n if (content.includes('name=\"description\"') || content.includes('property=\"description\"') || content.includes('<SEO')) {\n result.missingMetaTags.description = true;\n }\n if (content.includes('property=\"og:image\"') || content.includes('<SEO')) result.missingMetaTags.ogImage = true;\n }\n }\n\n return result;\n}\n\nfunction getAllFiles(dirPath: string, arrayOfFiles: string[] = []) {\n if (!fs.existsSync(dirPath)) return [];\n const files = fs.readdirSync(dirPath);\n\n files.forEach((file) => {\n if (fs.statSync(path.join(dirPath, file)).isDirectory()) {\n if (file !== 'node_modules' && file !== '.astro') {\n arrayOfFiles = getAllFiles(path.join(dirPath, file), arrayOfFiles);\n }\n } else {\n arrayOfFiles.push(path.join(dirPath, file));\n }\n });\n\n return arrayOfFiles;\n}\n","import { AuditResult } from './scanner.js';\n\nexport interface ScoreBreakdown {\n performance: number;\n accessibility: number;\n seo: number;\n compliance: number;\n optimization: number;\n bundle: number;\n total: number;\n}\n\nexport function calculateScore(result: AuditResult): ScoreBreakdown {\n // Weights based on Blueprint v1.3\n const weights = {\n performance: 0.30,\n accessibility: 0.20,\n seo: 0.20,\n compliance: 0.15,\n optimization: 0.10,\n bundle: 0.05\n };\n\n // 1. Performance (Initial 100, -10 per missing alt - proxy for poor hygiene)\n let perf = 100 - (result.missingAltTags * 10);\n perf = Math.max(0, perf);\n\n // 2. Accessibility (-20 per missing alt)\n let a11y = 100 - (result.missingAltTags * 20);\n a11y = Math.max(0, a11y);\n\n // 3. SEO (Weighted based on meta presence)\n let seo = 0;\n if (result.missingMetaTags.title) seo += 40;\n if (result.missingMetaTags.description) seo += 40;\n if (result.missingMetaTags.ogImage) seo += 20;\n\n // 4. Compliance (Hardcoded values penalty)\n let compliance = 100 - (result.hardcodedValues * 5);\n compliance = Math.max(0, compliance);\n\n // 5. Optimization (Placeholder for media optimization audit)\n let optimization = 100; // Default until deep media audit implemented\n\n // 6. Bundle Size (Heuristic)\n let bundle = 100;\n if (result.totalBuildSize > 10 * 1024 * 1024) bundle = 40; // Penalty > 10MB\n\n const total = Math.round(\n (perf * weights.performance) +\n (a11y * weights.accessibility) +\n (seo * weights.seo) +\n (compliance * weights.compliance) +\n (optimization * weights.optimization) +\n (bundle * weights.bundle)\n );\n\n return {\n performance: perf,\n accessibility: a11y,\n seo,\n compliance,\n optimization,\n bundle,\n total\n };\n}\n","import fs from 'fs';\r\nimport path from 'path';\r\nimport { scanSource } from './audit/scanner.js';\r\nimport { calculateScore, ScoreBreakdown } from './audit/scorer.js';\r\nimport { createPublicClient } from './client.js';\r\n\r\nexport { createPublicClient };\r\n\r\nexport function snappyCreditEnforcer() {\r\n return {\r\n name: 'snappy-credit-enforcer',\r\n async buildStart() {\r\n const srcDir = path.resolve(process.cwd(), 'src');\r\n const files = getAllFiles(srcDir);\r\n let hasCredit = false;\r\n\r\n for (const file of files) {\r\n if (file.endsWith('.astro') || file.endsWith('.tsx') || file.endsWith('.jsx')) {\r\n const content = fs.readFileSync(file, 'utf8');\r\n if (content.includes('SnappyCredit') || content.includes('<SnappyCredit')) {\r\n hasCredit = true;\r\n break;\r\n }\r\n }\r\n }\r\n\r\n if (!hasCredit) {\r\n throw new Error(\r\n '\\n\\n\\x1b[31m[SNAPPY_SDK_E401]\\x1b[0m\\nBuild validation failed: Mandatory <SnappyCredit /> component missing.\\n\\n'\r\n );\r\n }\r\n },\r\n\r\n async buildEnd() {\r\n const srcDir = path.resolve(process.cwd(), 'src');\r\n const auditResult = scanSource(srcDir);\r\n const score = calculateScore(auditResult);\r\n\r\n printReport(score);\r\n\r\n if (score.total < 50) {\r\n console.error('\\n\\x1b[31mš¹ BLOCKED:\\x1b[0m SNAPPY Score is below the quality threshold (50).');\r\n console.error('Ship clean or don\\'t ship at all. š«š”ļøš\\n');\r\n process.exit(1);\r\n }\r\n\r\n // Sync score with Core API if configured\r\n const token = process.env.SNAPPY_TOKEN;\r\n const baseUrl = process.env.SNAPPY_URL || 'https://core.wicky.id';\r\n if (token) {\r\n try {\r\n // Simple fetch to update score\r\n await fetch(`${baseUrl}/v1/projects/self/score`, {\r\n method: 'PATCH',\r\n headers: {\r\n 'Authorization': `Bearer ${token}`,\r\n 'Content-Type': 'application/json'\r\n },\r\n body: JSON.stringify({ score: score.total })\r\n });\r\n } catch (e) {\r\n // Silent fail on score sync - don't block build if API is down\r\n }\r\n }\r\n }\r\n };\r\n}\r\n\r\nfunction printReport(s: ScoreBreakdown) {\r\n const reset = '\\x1b[0m';\r\n const green = '\\x1b[32m';\r\n const yellow = '\\x1b[33m';\r\n const red = '\\x1b[31m';\r\n const cyan = '\\x1b[36m';\r\n\r\n const status = s.total >= 70 ? `${green}ā
PASS${reset}` : s.total >= 50 ? `${yellow}ā ļø WARNING${reset}` : `${red}š¹ BLOCKED${reset}`;\r\n\r\n console.log(`\r\n${cyan}āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\r\nā SNAPPY SCORE REPORT ā\r\nā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā£${reset}\r\nā Performance ${fmt(s.performance)} 30% ā\r\nā Accessibility ${fmt(s.accessibility)} 20% ā\r\nā SEO Completeness ${fmt(s.seo)} 20% ā\r\nā Zero Hardcoded ${fmt(s.compliance)} 15% ā\r\nā Image Optimization ${fmt(s.optimization)} 10% ā\r\nā Bundle Size ${fmt(s.bundle)} 5% ā\r\n${cyan}ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā£${reset}\r\nā TOTAL SCORE: ${s.total}/100 ā\r\nā STATUS: ${status} ā\r\n${cyan}āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā${reset}\r\n`);\r\n}\r\n\r\nfunction fmt(n: number) {\r\n const str = `${n}/100`;\r\n return str.padEnd(8, ' ');\r\n}\r\n\r\nfunction getAllFiles(dirPath: string, arrayOfFiles: string[] = []) {\r\n if (!fs.existsSync(dirPath)) return [];\r\n const files = fs.readdirSync(dirPath);\r\n files.forEach((file: string) => {\r\n if (fs.statSync(path.join(dirPath, file)).isDirectory()) {\r\n if (file !== 'node_modules' && file !== '.astro') {\r\n arrayOfFiles = getAllFiles(path.join(dirPath, file), arrayOfFiles);\r\n }\r\n } else {\r\n arrayOfFiles.push(path.join(dirPath, file));\r\n }\r\n });\r\n return arrayOfFiles;\r\n}\r\n"]}
|
package/package.json
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@snappy-stack/sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"engines": {
|
|
5
5
|
"node": ">=22"
|
|
6
6
|
},
|
|
7
|
-
"packageManager": "pnpm@9.15.0",
|
|
8
7
|
"license": "SEE LICENSE IN LICENSE",
|
|
9
8
|
"publishConfig": {
|
|
10
9
|
"registry": "https://registry.npmjs.org/",
|
|
11
10
|
"access": "public"
|
|
12
11
|
},
|
|
12
|
+
"bin": {
|
|
13
|
+
"snappy-check": "./dist/bin/check.js"
|
|
14
|
+
},
|
|
13
15
|
"files": [
|
|
14
16
|
"dist",
|
|
15
17
|
"README.md",
|
|
@@ -20,12 +22,25 @@
|
|
|
20
22
|
"url": "https://github.com/Snappy-Stack/Snappy_Stack"
|
|
21
23
|
},
|
|
22
24
|
"type": "module",
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"import": "./dist/index.js",
|
|
29
|
+
"require": "./dist/index.cjs"
|
|
30
|
+
},
|
|
31
|
+
"./plugin": {
|
|
32
|
+
"types": "./dist/plugin.d.ts",
|
|
33
|
+
"import": "./dist/plugin.js",
|
|
34
|
+
"require": "./dist/plugin.cjs"
|
|
35
|
+
},
|
|
36
|
+
"./check": {
|
|
37
|
+
"types": "./dist/bin/check.d.ts",
|
|
38
|
+
"import": "./dist/bin/check.js",
|
|
39
|
+
"require": "./dist/bin/check.cjs"
|
|
40
|
+
}
|
|
41
|
+
},
|
|
23
42
|
"main": "./dist/index.js",
|
|
24
43
|
"types": "./dist/index.d.ts",
|
|
25
|
-
"scripts": {
|
|
26
|
-
"build": "tsup",
|
|
27
|
-
"dev": "tsup --watch"
|
|
28
|
-
},
|
|
29
44
|
"devDependencies": {
|
|
30
45
|
"@types/node": "^22.0.0",
|
|
31
46
|
"@types/react": "^19.2.14",
|
|
@@ -34,5 +49,9 @@
|
|
|
34
49
|
},
|
|
35
50
|
"dependencies": {
|
|
36
51
|
"react": "^19.2.4"
|
|
52
|
+
},
|
|
53
|
+
"scripts": {
|
|
54
|
+
"build": "tsup",
|
|
55
|
+
"dev": "tsup --watch"
|
|
37
56
|
}
|
|
38
|
-
}
|
|
57
|
+
}
|