@decocms/start 0.19.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/.cursor/skills/deco-api-call-dedup/SKILL.md +443 -0
- package/.cursor/skills/deco-apps-architecture/SKILL.md +255 -0
- package/.cursor/skills/deco-apps-architecture/app-pattern.md +288 -0
- package/.cursor/skills/deco-apps-architecture/commerce-types.md +239 -0
- package/.cursor/skills/deco-apps-architecture/new-app-guide.md +268 -0
- package/.cursor/skills/deco-apps-architecture/scripts-codegen.md +148 -0
- package/.cursor/skills/deco-apps-architecture/shared-utils.md +181 -0
- package/.cursor/skills/deco-apps-architecture/vtex-deep-structure.md +253 -0
- package/.cursor/skills/deco-apps-architecture/website-app.md +169 -0
- package/.cursor/skills/deco-apps-vtex-porting/SKILL.md +189 -0
- package/.cursor/skills/deco-apps-vtex-porting/adaptation-patterns.md +335 -0
- package/.cursor/skills/deco-apps-vtex-porting/commerce-porting.md +155 -0
- package/.cursor/skills/deco-apps-vtex-porting/cookie-auth-patterns.md +148 -0
- package/.cursor/skills/deco-apps-vtex-porting/structure-map.md +234 -0
- package/.cursor/skills/deco-apps-vtex-porting/transform-mapping.md +99 -0
- package/.cursor/skills/deco-apps-vtex-porting/website-porting.md +194 -0
- package/.cursor/skills/deco-apps-vtex-review/SKILL.md +234 -0
- package/.cursor/skills/deco-async-rendering-architecture/SKILL.md +270 -0
- package/.cursor/skills/deco-async-rendering-site-guide/SKILL.md +417 -0
- package/.cursor/skills/deco-cms-layout-caching/SKILL.md +293 -0
- package/.cursor/skills/deco-cms-route-config/SKILL.md +388 -0
- package/.cursor/skills/deco-core-architecture/SKILL.md +185 -0
- package/.cursor/skills/deco-core-architecture/blocks.md +196 -0
- package/.cursor/skills/deco-core-architecture/deco-vs-deco-start.md +191 -0
- package/.cursor/skills/deco-core-architecture/engine.md +220 -0
- package/.cursor/skills/deco-core-architecture/hooks-components.md +157 -0
- package/.cursor/skills/deco-core-architecture/plugins-clients.md +136 -0
- package/.cursor/skills/deco-core-architecture/runtime.md +116 -0
- package/.cursor/skills/deco-core-architecture/site-usage.md +165 -0
- package/.cursor/skills/deco-e2e-testing/SKILL.md +372 -0
- package/.cursor/skills/deco-e2e-testing/discovery.md +337 -0
- package/.cursor/skills/deco-e2e-testing/scripts/scaffold.sh +81 -0
- package/.cursor/skills/deco-e2e-testing/selectors.md +175 -0
- package/.cursor/skills/deco-e2e-testing/templates/package.json +18 -0
- package/.cursor/skills/deco-e2e-testing/templates/playwright.config.ts +65 -0
- package/.cursor/skills/deco-e2e-testing/templates/scripts/baseline.ts +279 -0
- package/.cursor/skills/deco-e2e-testing/templates/scripts/run-e2e.ts +194 -0
- package/.cursor/skills/deco-e2e-testing/templates/specs/ecommerce-flow.spec.ts +612 -0
- package/.cursor/skills/deco-e2e-testing/templates/tsconfig.json +12 -0
- package/.cursor/skills/deco-e2e-testing/templates/utils/metrics-collector.ts +918 -0
- package/.cursor/skills/deco-e2e-testing/troubleshooting.md +602 -0
- package/.cursor/skills/deco-edge-caching/SKILL.md +316 -0
- package/.cursor/skills/deco-full-analysis/SKILL.md +898 -0
- package/.cursor/skills/deco-full-analysis/checklists/asset-optimization.md +251 -0
- package/.cursor/skills/deco-full-analysis/checklists/bug-fix.md +189 -0
- package/.cursor/skills/deco-full-analysis/checklists/cache-strategy.md +144 -0
- package/.cursor/skills/deco-full-analysis/checklists/dependency-update.md +150 -0
- package/.cursor/skills/deco-full-analysis/checklists/hydration-fix.md +191 -0
- package/.cursor/skills/deco-full-analysis/checklists/image-optimization.md +180 -0
- package/.cursor/skills/deco-full-analysis/checklists/loader-optimization.md +165 -0
- package/.cursor/skills/deco-full-analysis/checklists/seo-fix.md +183 -0
- package/.cursor/skills/deco-full-analysis/checklists/site-cleanup.md +281 -0
- package/.cursor/skills/deco-full-analysis/discovery.md +548 -0
- package/.cursor/skills/deco-incident-debugging/SKILL.md +378 -0
- package/.cursor/skills/deco-incident-debugging/headless-mode.md +510 -0
- package/.cursor/skills/deco-incident-debugging/learnings-index.md +227 -0
- package/.cursor/skills/deco-incident-debugging/triage-workflow.md +312 -0
- package/.cursor/skills/deco-islands-migration/SKILL.md +251 -0
- package/.cursor/skills/deco-loader-n-plus-1-detector/SKILL.md +275 -0
- package/.cursor/skills/deco-performance-audit/SKILL.md +530 -0
- package/.cursor/skills/deco-performance-audit/tools-reference.md +428 -0
- package/.cursor/skills/deco-performance-audit/workflow.md +457 -0
- package/.cursor/skills/deco-server-functions-invoke/SKILL.md +92 -0
- package/.cursor/skills/deco-server-functions-invoke/architecture.md +166 -0
- package/.cursor/skills/deco-server-functions-invoke/generator.md +122 -0
- package/.cursor/skills/deco-server-functions-invoke/problem.md +98 -0
- package/.cursor/skills/deco-server-functions-invoke/troubleshooting.md +110 -0
- package/.cursor/skills/deco-site-deployment/SKILL.md +396 -0
- package/.cursor/skills/deco-site-memory-debugging/SKILL.md +121 -0
- package/.cursor/skills/deco-site-memory-debugging/cdp-connection.md +222 -0
- package/.cursor/skills/deco-site-memory-debugging/memory-analysis.md +362 -0
- package/.cursor/skills/deco-site-patterns/SKILL.md +124 -0
- package/.cursor/skills/deco-site-patterns/app-composition.md +337 -0
- package/.cursor/skills/deco-site-patterns/client-patterns.md +341 -0
- package/.cursor/skills/deco-site-patterns/cms-wiring.md +230 -0
- package/.cursor/skills/deco-site-patterns/section-patterns.md +340 -0
- package/.cursor/skills/deco-site-scaling-tuning/SKILL.md +240 -0
- package/.cursor/skills/deco-site-scaling-tuning/analysis-scripts.md +267 -0
- package/.cursor/skills/deco-start-architecture/SKILL.md +218 -0
- package/.cursor/skills/deco-start-architecture/admin-protocol.md +156 -0
- package/.cursor/skills/deco-start-architecture/cms-resolution.md +201 -0
- package/.cursor/skills/deco-start-architecture/code-quality.md +158 -0
- package/.cursor/skills/deco-start-architecture/gap-analysis.md +129 -0
- package/.cursor/skills/deco-start-architecture/sdk-utilities.md +197 -0
- package/.cursor/skills/deco-start-architecture/worker-entry-caching.md +154 -0
- package/.cursor/skills/deco-startup-analysis/SKILL.md +248 -0
- package/.cursor/skills/deco-storefront-test-checklist/SKILL.md +369 -0
- package/.cursor/skills/deco-tanstack-hydration-fixes/SKILL.md +468 -0
- package/.cursor/skills/deco-tanstack-navigation/SKILL.md +681 -0
- package/.cursor/skills/deco-tanstack-search/SKILL.md +411 -0
- package/.cursor/skills/deco-tanstack-storefront-patterns/SKILL.md +1013 -0
- package/.cursor/skills/deco-to-tanstack-migration/SKILL.md +518 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/codemod-commands.md +174 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/commerce/README.md +78 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/deco-framework/README.md +128 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/gotchas.md +719 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/imports/README.md +70 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/platform-hooks/README.md +154 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/signals/README.md +220 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/vite-config/README.md +78 -0
- package/.cursor/skills/deco-to-tanstack-migration/templates/package-json.md +55 -0
- package/.cursor/skills/deco-to-tanstack-migration/templates/root-route.md +110 -0
- package/.cursor/skills/deco-to-tanstack-migration/templates/router.md +96 -0
- package/.cursor/skills/deco-to-tanstack-migration/templates/setup-ts.md +167 -0
- package/.cursor/skills/deco-to-tanstack-migration/templates/vite-config.md +122 -0
- package/.cursor/skills/deco-to-tanstack-migration/templates/worker-entry.md +67 -0
- package/.cursor/skills/deco-typescript-fixes/SKILL.md +178 -0
- package/.cursor/skills/deco-typescript-fixes/common-fixes.md +330 -0
- package/.cursor/skills/deco-typescript-fixes/strategy.md +148 -0
- package/.cursor/skills/deco-variant-selection-perf/SKILL.md +272 -0
- package/.cursor/skills/deco-vtex-fetch-cache/SKILL.md +225 -0
- package/.cursor/skills/find-skills/SKILL.md +133 -0
- package/.cursor/skills/incident-report/SKILL.md +179 -0
- package/.cursor/skills/incident-report/references/5-whys.md +75 -0
- package/.cursor/skills/incident-report/templates/client-report.md +187 -0
- package/.cursor/skills/incident-report/templates/internal-report.md +206 -0
- package/.cursor/skills/template-skill/SKILL.md +38 -0
- package/.github/workflows/release.yml +32 -0
- package/.releaserc.json +25 -0
- package/CLAUDE.md +135 -0
- package/GAP_ANALYSIS.md +224 -0
- package/GAP_ANALYSIS_V2.md +1013 -0
- package/biome.json +39 -0
- package/knip.json +5 -0
- package/package.json +87 -0
- package/scripts/generate-blocks.ts +69 -0
- package/scripts/generate-invoke.ts +378 -0
- package/scripts/generate-schema.ts +657 -0
- package/src/admin/cors.ts +29 -0
- package/src/admin/decofile.ts +72 -0
- package/src/admin/index.ts +24 -0
- package/src/admin/invoke.ts +163 -0
- package/src/admin/liveControls.ts +29 -0
- package/src/admin/meta.ts +70 -0
- package/src/admin/render.ts +205 -0
- package/src/admin/schema.ts +686 -0
- package/src/admin/setup.ts +44 -0
- package/src/cms/index.ts +59 -0
- package/src/cms/loader.ts +180 -0
- package/src/cms/registry.ts +162 -0
- package/src/cms/resolve.ts +1005 -0
- package/src/cms/sectionLoaders.ts +294 -0
- package/src/hooks/DecoPageRenderer.tsx +444 -0
- package/src/hooks/LazySection.tsx +109 -0
- package/src/hooks/LiveControls.tsx +108 -0
- package/src/hooks/SectionErrorFallback.tsx +85 -0
- package/src/hooks/index.ts +8 -0
- package/src/index.ts +5 -0
- package/src/matchers/builtins.ts +184 -0
- package/src/matchers/posthog.ts +154 -0
- package/src/middleware/decoState.ts +55 -0
- package/src/middleware/healthMetrics.ts +131 -0
- package/src/middleware/index.ts +80 -0
- package/src/middleware/liveness.ts +21 -0
- package/src/middleware/observability.ts +205 -0
- package/src/routes/adminRoutes.ts +83 -0
- package/src/routes/cmsRoute.ts +302 -0
- package/src/routes/components.tsx +34 -0
- package/src/routes/index.ts +15 -0
- package/src/sdk/analytics.ts +72 -0
- package/src/sdk/cacheHeaders.ts +268 -0
- package/src/sdk/cachedLoader.ts +206 -0
- package/src/sdk/clx.ts +3 -0
- package/src/sdk/cookie.ts +39 -0
- package/src/sdk/createInvoke.ts +57 -0
- package/src/sdk/csp.ts +59 -0
- package/src/sdk/env.ts +27 -0
- package/src/sdk/index.ts +63 -0
- package/src/sdk/instrumentedFetch.ts +137 -0
- package/src/sdk/invoke.ts +133 -0
- package/src/sdk/mergeCacheControl.ts +150 -0
- package/src/sdk/redirects.ts +217 -0
- package/src/sdk/requestContext.ts +184 -0
- package/src/sdk/serverTimings.ts +68 -0
- package/src/sdk/signal.ts +41 -0
- package/src/sdk/sitemap.ts +143 -0
- package/src/sdk/urlUtils.ts +117 -0
- package/src/sdk/useDevice.ts +82 -0
- package/src/sdk/useId.ts +7 -0
- package/src/sdk/useScript.ts +101 -0
- package/src/sdk/workerEntry.ts +703 -0
- package/src/sdk/wrapCaughtErrors.ts +107 -0
- package/src/types/index.ts +39 -0
- package/src/types/widgets.ts +13 -0
- package/tsconfig.json +13 -0
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
#!/usr/bin/env -S deno run -A
|
|
2
|
+
/**
|
|
3
|
+
* Baseline Management Script
|
|
4
|
+
* ==========================
|
|
5
|
+
* Save current test results as baseline and compare against it.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* deno run -A scripts/baseline.ts save [name]
|
|
9
|
+
* deno run -A scripts/baseline.ts compare [name]
|
|
10
|
+
* deno run -A scripts/baseline.ts list
|
|
11
|
+
* deno run -A scripts/baseline.ts delete <name>
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { ensureDirSync, existsSync } from "https://deno.land/std@0.224.0/fs/mod.ts";
|
|
15
|
+
import { join, dirname, fromFileUrl } from "https://deno.land/std@0.224.0/path/mod.ts";
|
|
16
|
+
|
|
17
|
+
interface PerformanceMetrics {
|
|
18
|
+
TTFB: number | null
|
|
19
|
+
FCP: number | null
|
|
20
|
+
LCP: number | null
|
|
21
|
+
CLS: number | null
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface PageMetrics {
|
|
25
|
+
pageName: string
|
|
26
|
+
performance: PerformanceMetrics
|
|
27
|
+
network: { totalRequests: number; totalBytes: number }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface Report {
|
|
31
|
+
metrics: PageMetrics[]
|
|
32
|
+
timestamp: string
|
|
33
|
+
savedAt?: string
|
|
34
|
+
name?: string
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface ComparisonResult {
|
|
38
|
+
pageName: string
|
|
39
|
+
metric: string
|
|
40
|
+
baseline: number
|
|
41
|
+
current: number
|
|
42
|
+
diff: number
|
|
43
|
+
diffPercent: number
|
|
44
|
+
status: 'improved' | 'regressed' | 'unchanged'
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const SCRIPT_DIR = dirname(fromFileUrl(import.meta.url))
|
|
48
|
+
const REPORTS_DIR = join(SCRIPT_DIR, '..', 'reports')
|
|
49
|
+
const BASELINES_DIR = join(REPORTS_DIR, 'baselines')
|
|
50
|
+
const LATEST_REPORT = join(REPORTS_DIR, 'report-latest.json')
|
|
51
|
+
const DEFAULT_BASELINE = join(BASELINES_DIR, 'baseline.json')
|
|
52
|
+
|
|
53
|
+
// Threshold percentages for regression detection
|
|
54
|
+
const THRESHOLDS: Record<string, number> = {
|
|
55
|
+
TTFB: 10, // 10% slower = regression
|
|
56
|
+
FCP: 10,
|
|
57
|
+
LCP: 15,
|
|
58
|
+
CLS: 50, // CLS is more variable
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function loadReport(filepath: string): Report | null {
|
|
62
|
+
try {
|
|
63
|
+
const content = Deno.readTextFileSync(filepath)
|
|
64
|
+
return JSON.parse(content)
|
|
65
|
+
} catch {
|
|
66
|
+
return null
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function saveBaseline(name?: string): void {
|
|
71
|
+
ensureDirSync(BASELINES_DIR)
|
|
72
|
+
|
|
73
|
+
const latest = loadReport(LATEST_REPORT)
|
|
74
|
+
if (!latest) {
|
|
75
|
+
console.error('❌ No report found at', LATEST_REPORT)
|
|
76
|
+
console.error(' Run the e2e tests first: deno task test:e2e')
|
|
77
|
+
Deno.exit(1)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const targetPath = name
|
|
81
|
+
? join(BASELINES_DIR, `baseline-${name}.json`)
|
|
82
|
+
: DEFAULT_BASELINE
|
|
83
|
+
|
|
84
|
+
const baseline: Report = {
|
|
85
|
+
...latest,
|
|
86
|
+
savedAt: new Date().toISOString(),
|
|
87
|
+
name: name || 'default',
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
Deno.writeTextFileSync(targetPath, JSON.stringify(baseline, null, 2))
|
|
91
|
+
console.log(`✅ Baseline saved: ${targetPath}`)
|
|
92
|
+
console.log(` Timestamp: ${latest.timestamp}`)
|
|
93
|
+
console.log(` Pages: ${latest.metrics.map(m => m.pageName).join(', ')}`)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function listBaselines(): void {
|
|
97
|
+
ensureDirSync(BASELINES_DIR)
|
|
98
|
+
|
|
99
|
+
let files: string[] = []
|
|
100
|
+
try {
|
|
101
|
+
files = [...Deno.readDirSync(BASELINES_DIR)]
|
|
102
|
+
.filter(f => f.isFile && f.name.endsWith('.json'))
|
|
103
|
+
.map(f => f.name)
|
|
104
|
+
} catch {
|
|
105
|
+
// Directory might not exist yet
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (files.length === 0) {
|
|
109
|
+
console.log('📋 No baselines saved yet')
|
|
110
|
+
console.log(' Save one with: deno task test:e2e:baseline:save')
|
|
111
|
+
return
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
console.log('📋 Saved baselines:\n')
|
|
115
|
+
for (const file of files) {
|
|
116
|
+
const filepath = join(BASELINES_DIR, file)
|
|
117
|
+
const baseline = loadReport(filepath)
|
|
118
|
+
if (baseline) {
|
|
119
|
+
const name = file.replace('baseline-', '').replace('.json', '')
|
|
120
|
+
console.log(` ${name}`)
|
|
121
|
+
console.log(` Saved: ${baseline.savedAt || baseline.timestamp}`)
|
|
122
|
+
console.log(` Pages: ${baseline.metrics.length}`)
|
|
123
|
+
console.log('')
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function deleteBaseline(name: string): void {
|
|
129
|
+
const targetPath = name === 'default'
|
|
130
|
+
? DEFAULT_BASELINE
|
|
131
|
+
: join(BASELINES_DIR, `baseline-${name}.json`)
|
|
132
|
+
|
|
133
|
+
if (!existsSync(targetPath)) {
|
|
134
|
+
console.error(`❌ Baseline not found: ${name}`)
|
|
135
|
+
Deno.exit(1)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
Deno.removeSync(targetPath)
|
|
139
|
+
console.log(`✅ Deleted baseline: ${name}`)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function compare(baselineName?: string): void {
|
|
143
|
+
const baselinePath = baselineName
|
|
144
|
+
? join(BASELINES_DIR, `baseline-${baselineName}.json`)
|
|
145
|
+
: DEFAULT_BASELINE
|
|
146
|
+
|
|
147
|
+
const baseline = loadReport(baselinePath)
|
|
148
|
+
if (!baseline) {
|
|
149
|
+
console.error('❌ No baseline found at', baselinePath)
|
|
150
|
+
console.error(' Save a baseline first: deno task test:e2e:baseline:save')
|
|
151
|
+
Deno.exit(1)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const current = loadReport(LATEST_REPORT)
|
|
155
|
+
if (!current) {
|
|
156
|
+
console.error('❌ No current report found at', LATEST_REPORT)
|
|
157
|
+
console.error(' Run the e2e tests first: deno task test:e2e')
|
|
158
|
+
Deno.exit(1)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
console.log('📊 Performance Comparison\n')
|
|
162
|
+
console.log(` Baseline: ${baseline.savedAt || baseline.timestamp}`)
|
|
163
|
+
console.log(` Current: ${current.timestamp}\n`)
|
|
164
|
+
|
|
165
|
+
const results: ComparisonResult[] = []
|
|
166
|
+
let hasRegression = false
|
|
167
|
+
|
|
168
|
+
// Compare each page
|
|
169
|
+
for (const currentPage of current.metrics) {
|
|
170
|
+
const baselinePage = baseline.metrics.find(m => m.pageName === currentPage.pageName)
|
|
171
|
+
if (!baselinePage) continue
|
|
172
|
+
|
|
173
|
+
// Compare key metrics
|
|
174
|
+
for (const metric of ['TTFB', 'FCP', 'LCP'] as const) {
|
|
175
|
+
const baseVal = baselinePage.performance[metric]
|
|
176
|
+
const currVal = currentPage.performance[metric]
|
|
177
|
+
|
|
178
|
+
if (baseVal === null || currVal === null) continue
|
|
179
|
+
|
|
180
|
+
const diff = currVal - baseVal
|
|
181
|
+
const diffPercent = (diff / baseVal) * 100
|
|
182
|
+
const threshold = THRESHOLDS[metric]
|
|
183
|
+
|
|
184
|
+
let status: 'improved' | 'regressed' | 'unchanged'
|
|
185
|
+
if (diffPercent < -threshold) {
|
|
186
|
+
status = 'improved'
|
|
187
|
+
} else if (diffPercent > threshold) {
|
|
188
|
+
status = 'regressed'
|
|
189
|
+
hasRegression = true
|
|
190
|
+
} else {
|
|
191
|
+
status = 'unchanged'
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
results.push({
|
|
195
|
+
pageName: currentPage.pageName,
|
|
196
|
+
metric,
|
|
197
|
+
baseline: baseVal,
|
|
198
|
+
current: currVal,
|
|
199
|
+
diff,
|
|
200
|
+
diffPercent,
|
|
201
|
+
status,
|
|
202
|
+
})
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Print results grouped by page
|
|
207
|
+
const pageNames = [...new Set(results.map(r => r.pageName))]
|
|
208
|
+
for (const pageName of pageNames) {
|
|
209
|
+
console.log(` ${pageName}`)
|
|
210
|
+
const pageResults = results.filter(r => r.pageName === pageName)
|
|
211
|
+
|
|
212
|
+
for (const r of pageResults) {
|
|
213
|
+
const icon = r.status === 'improved' ? '✅' : r.status === 'regressed' ? '❌' : '➖'
|
|
214
|
+
const sign = r.diff > 0 ? '+' : ''
|
|
215
|
+
const diffStr = `${sign}${r.diff.toFixed(0)}ms (${sign}${r.diffPercent.toFixed(1)}%)`
|
|
216
|
+
|
|
217
|
+
console.log(` ${icon} ${r.metric}: ${r.baseline.toFixed(0)}ms → ${r.current.toFixed(0)}ms ${diffStr}`)
|
|
218
|
+
}
|
|
219
|
+
console.log('')
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Summary
|
|
223
|
+
const improved = results.filter(r => r.status === 'improved').length
|
|
224
|
+
const regressed = results.filter(r => r.status === 'regressed').length
|
|
225
|
+
const unchanged = results.filter(r => r.status === 'unchanged').length
|
|
226
|
+
|
|
227
|
+
console.log(' Summary:')
|
|
228
|
+
console.log(` ✅ Improved: ${improved}`)
|
|
229
|
+
console.log(` ❌ Regressed: ${regressed}`)
|
|
230
|
+
console.log(` ➖ Unchanged: ${unchanged}`)
|
|
231
|
+
|
|
232
|
+
if (hasRegression) {
|
|
233
|
+
console.log('\n⚠️ Performance regressions detected!')
|
|
234
|
+
Deno.exit(1)
|
|
235
|
+
} else {
|
|
236
|
+
console.log('\n✅ No regressions detected')
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// CLI
|
|
241
|
+
const [command, arg] = Deno.args
|
|
242
|
+
|
|
243
|
+
switch (command) {
|
|
244
|
+
case 'save':
|
|
245
|
+
saveBaseline(arg)
|
|
246
|
+
break
|
|
247
|
+
case 'compare':
|
|
248
|
+
compare(arg)
|
|
249
|
+
break
|
|
250
|
+
case 'list':
|
|
251
|
+
listBaselines()
|
|
252
|
+
break
|
|
253
|
+
case 'delete':
|
|
254
|
+
if (!arg) {
|
|
255
|
+
console.error('❌ Please specify baseline name to delete')
|
|
256
|
+
Deno.exit(1)
|
|
257
|
+
}
|
|
258
|
+
deleteBaseline(arg)
|
|
259
|
+
break
|
|
260
|
+
default:
|
|
261
|
+
console.log(`
|
|
262
|
+
Baseline Management Script
|
|
263
|
+
==========================
|
|
264
|
+
|
|
265
|
+
Commands:
|
|
266
|
+
save [name] Save current report as baseline (default name: "default")
|
|
267
|
+
compare [name] Compare current report against baseline
|
|
268
|
+
list List all saved baselines
|
|
269
|
+
delete <name> Delete a specific baseline
|
|
270
|
+
|
|
271
|
+
Examples:
|
|
272
|
+
deno task test:e2e:baseline:save
|
|
273
|
+
deno task test:e2e:baseline:compare
|
|
274
|
+
|
|
275
|
+
# With names:
|
|
276
|
+
deno run -A scripts/baseline.ts save pre-release
|
|
277
|
+
deno run -A scripts/baseline.ts compare pre-release
|
|
278
|
+
`)
|
|
279
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
#!/usr/bin/env -S deno run -A
|
|
2
|
+
/**
|
|
3
|
+
* E2E Test Runner Script
|
|
4
|
+
* ======================
|
|
5
|
+
* This script:
|
|
6
|
+
* 1. Checks if the dev server is already running on port 8000
|
|
7
|
+
* 2. If not, starts it in the background
|
|
8
|
+
* 3. Waits for the server to be ready (liveness check)
|
|
9
|
+
* 4. Runs the e2e tests
|
|
10
|
+
* 5. Reports the results
|
|
11
|
+
* 6. Cleans up server on exit (including Ctrl+C)
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* deno task test:e2e # Run tests (auto-start server if needed)
|
|
15
|
+
* deno task test:e2e:headed # Run tests with visible browser
|
|
16
|
+
*
|
|
17
|
+
* Placeholders to replace:
|
|
18
|
+
* {{SITE_NAME}} - Your site name (e.g., "lojastorra-2")
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const SITE_URL = "http://localhost:8000";
|
|
22
|
+
const LIVENESS_PATH = "/deco/_liveness";
|
|
23
|
+
const E2E_DIR = "./tests/e2e";
|
|
24
|
+
const MAX_LIVENESS_RETRIES = 60;
|
|
25
|
+
const LIVENESS_RETRY_DELAY = 1000;
|
|
26
|
+
|
|
27
|
+
// Global state for cleanup
|
|
28
|
+
let serverProcess: Deno.ChildProcess | null = null;
|
|
29
|
+
let serverStartedByUs = false;
|
|
30
|
+
let isCleaningUp = false;
|
|
31
|
+
|
|
32
|
+
function cleanup(exitCode: number = 1): void {
|
|
33
|
+
if (isCleaningUp) return;
|
|
34
|
+
isCleaningUp = true;
|
|
35
|
+
|
|
36
|
+
if (serverProcess && serverStartedByUs) {
|
|
37
|
+
console.log("\n🛑 Stopping dev server...");
|
|
38
|
+
try {
|
|
39
|
+
serverProcess.kill("SIGTERM");
|
|
40
|
+
} catch {
|
|
41
|
+
// Process may already be dead
|
|
42
|
+
}
|
|
43
|
+
// Give it a moment to terminate gracefully, then force kill
|
|
44
|
+
setTimeout(() => {
|
|
45
|
+
try {
|
|
46
|
+
serverProcess?.kill("SIGKILL");
|
|
47
|
+
} catch {
|
|
48
|
+
// Ignore
|
|
49
|
+
}
|
|
50
|
+
Deno.exit(exitCode);
|
|
51
|
+
}, 1000);
|
|
52
|
+
} else {
|
|
53
|
+
Deno.exit(exitCode);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Handle Ctrl+C and other termination signals
|
|
58
|
+
Deno.addSignalListener("SIGINT", () => {
|
|
59
|
+
console.log("\n⚠️ Interrupted (Ctrl+C)");
|
|
60
|
+
cleanup(130); // Standard exit code for SIGINT
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
Deno.addSignalListener("SIGTERM", () => {
|
|
64
|
+
console.log("\n⚠️ Terminated");
|
|
65
|
+
cleanup(143); // Standard exit code for SIGTERM
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Handle uncaught errors
|
|
69
|
+
globalThis.addEventListener("unhandledrejection", (event) => {
|
|
70
|
+
console.error("❌ Unhandled error:", event.reason);
|
|
71
|
+
cleanup(1);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
async function isServerRunning(): Promise<boolean> {
|
|
75
|
+
try {
|
|
76
|
+
const res = await fetch(`${SITE_URL}${LIVENESS_PATH}`, {
|
|
77
|
+
signal: AbortSignal.timeout(2000),
|
|
78
|
+
});
|
|
79
|
+
return res.ok;
|
|
80
|
+
} catch {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function waitForServer(): Promise<boolean> {
|
|
86
|
+
console.log("⏳ Waiting for server to be ready...");
|
|
87
|
+
|
|
88
|
+
for (let i = 0; i < MAX_LIVENESS_RETRIES; i++) {
|
|
89
|
+
if (await isServerRunning()) {
|
|
90
|
+
console.log(`✅ Server is ready (attempt ${i + 1})`);
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
await new Promise((r) => setTimeout(r, LIVENESS_RETRY_DELAY));
|
|
94
|
+
if ((i + 1) % 10 === 0) {
|
|
95
|
+
console.log(` Still waiting... (${i + 1}/${MAX_LIVENESS_RETRIES})`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function startServer(): Promise<Deno.ChildProcess> {
|
|
103
|
+
console.log("🚀 Starting dev server...");
|
|
104
|
+
|
|
105
|
+
const command = new Deno.Command("deno", {
|
|
106
|
+
args: ["task", "dev"],
|
|
107
|
+
stdout: "piped",
|
|
108
|
+
stderr: "piped",
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const process = command.spawn();
|
|
112
|
+
|
|
113
|
+
// Log server output in background
|
|
114
|
+
(async () => {
|
|
115
|
+
const decoder = new TextDecoder();
|
|
116
|
+
for await (const chunk of process.stdout) {
|
|
117
|
+
const text = decoder.decode(chunk);
|
|
118
|
+
if (text.includes("Fresh ready") || text.includes("Listening")) {
|
|
119
|
+
console.log(" 📡 Server started");
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
})();
|
|
123
|
+
|
|
124
|
+
return process;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function runTests(headed: boolean = false): Promise<boolean> {
|
|
128
|
+
console.log(`\n🧪 Running e2e tests${headed ? " (headed mode)" : ""}...\n`);
|
|
129
|
+
|
|
130
|
+
const args = ["test", "--project=desktop-chrome"];
|
|
131
|
+
if (headed) {
|
|
132
|
+
args.push("--headed");
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const command = new Deno.Command("npm", {
|
|
136
|
+
args,
|
|
137
|
+
cwd: E2E_DIR,
|
|
138
|
+
env: {
|
|
139
|
+
...Deno.env.toObject(),
|
|
140
|
+
SITE_URL,
|
|
141
|
+
HEADED: headed ? "true" : "false",
|
|
142
|
+
},
|
|
143
|
+
stdout: "inherit",
|
|
144
|
+
stderr: "inherit",
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
const { code } = await command.output();
|
|
148
|
+
return code === 0;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function main() {
|
|
152
|
+
const args = Deno.args;
|
|
153
|
+
const headed = args.includes("--headed") || args.includes("-h");
|
|
154
|
+
const skipServerCheck = args.includes("--skip-server-check");
|
|
155
|
+
|
|
156
|
+
try {
|
|
157
|
+
// Check if server is already running
|
|
158
|
+
if (!skipServerCheck) {
|
|
159
|
+
const serverAlreadyRunning = await isServerRunning();
|
|
160
|
+
|
|
161
|
+
if (serverAlreadyRunning) {
|
|
162
|
+
console.log("✅ Dev server already running");
|
|
163
|
+
} else {
|
|
164
|
+
// Start the server
|
|
165
|
+
serverProcess = await startServer();
|
|
166
|
+
serverStartedByUs = true;
|
|
167
|
+
|
|
168
|
+
// Wait for it to be ready
|
|
169
|
+
const ready = await waitForServer();
|
|
170
|
+
if (!ready) {
|
|
171
|
+
console.error("❌ Server failed to start within timeout");
|
|
172
|
+
cleanup(1);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Run the tests
|
|
179
|
+
const testsPassed = await runTests(headed);
|
|
180
|
+
|
|
181
|
+
if (testsPassed) {
|
|
182
|
+
console.log("\n✅ All tests passed!");
|
|
183
|
+
} else {
|
|
184
|
+
console.log("\n❌ Some tests failed");
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
cleanup(testsPassed ? 0 : 1);
|
|
188
|
+
} catch (err) {
|
|
189
|
+
console.error("❌ Error:", err);
|
|
190
|
+
cleanup(1);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
main();
|