@snappy-stack/sdk 0.1.4 → 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/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.4",
3
+ "version": "0.1.6",
4
4
  "engines": {
5
- "node": "24.x"
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
+ }