@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
package/biome.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://biomejs.dev/schemas/2.0/schema.json",
|
|
3
|
+
"assist": {
|
|
4
|
+
"actions": {
|
|
5
|
+
"source": {
|
|
6
|
+
"organizeImports": "on"
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
"linter": {
|
|
11
|
+
"enabled": true,
|
|
12
|
+
"rules": {
|
|
13
|
+
"recommended": true,
|
|
14
|
+
"correctness": {
|
|
15
|
+
"noUnusedVariables": "warn",
|
|
16
|
+
"noUnusedImports": "warn"
|
|
17
|
+
},
|
|
18
|
+
"suspicious": {
|
|
19
|
+
"noExplicitAny": "off",
|
|
20
|
+
"noArrayIndexKey": "off"
|
|
21
|
+
},
|
|
22
|
+
"security": {
|
|
23
|
+
"noDangerouslySetInnerHtml": "off"
|
|
24
|
+
},
|
|
25
|
+
"style": {
|
|
26
|
+
"noNonNullAssertion": "off"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"formatter": {
|
|
31
|
+
"enabled": true,
|
|
32
|
+
"indentStyle": "space",
|
|
33
|
+
"indentWidth": 2,
|
|
34
|
+
"lineWidth": 100
|
|
35
|
+
},
|
|
36
|
+
"files": {
|
|
37
|
+
"includes": ["src/**", "scripts/**"]
|
|
38
|
+
}
|
|
39
|
+
}
|
package/knip.json
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@decocms/start",
|
|
3
|
+
"version": "0.19.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Deco framework for TanStack Start - CMS bridge, admin protocol, hooks, schema generation",
|
|
6
|
+
"main": "./src/index.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/index.ts",
|
|
9
|
+
"./cms": "./src/cms/index.ts",
|
|
10
|
+
"./admin": "./src/admin/index.ts",
|
|
11
|
+
"./hooks": "./src/hooks/index.ts",
|
|
12
|
+
"./types": "./src/types/index.ts",
|
|
13
|
+
"./sdk": "./src/sdk/index.ts",
|
|
14
|
+
"./sdk/useScript": "./src/sdk/useScript.ts",
|
|
15
|
+
"./sdk/signal": "./src/sdk/signal.ts",
|
|
16
|
+
"./sdk/clx": "./src/sdk/clx.ts",
|
|
17
|
+
"./sdk/useId": "./src/sdk/useId.ts",
|
|
18
|
+
"./sdk/cookie": "./src/sdk/cookie.ts",
|
|
19
|
+
"./sdk/analytics": "./src/sdk/analytics.ts",
|
|
20
|
+
"./sdk/cachedLoader": "./src/sdk/cachedLoader.ts",
|
|
21
|
+
"./sdk/serverTimings": "./src/sdk/serverTimings.ts",
|
|
22
|
+
"./sdk/cacheHeaders": "./src/sdk/cacheHeaders.ts",
|
|
23
|
+
"./sdk/invoke": "./src/sdk/invoke.ts",
|
|
24
|
+
"./sdk/instrumentedFetch": "./src/sdk/instrumentedFetch.ts",
|
|
25
|
+
"./sdk/workerEntry": "./src/sdk/workerEntry.ts",
|
|
26
|
+
"./sdk/redirects": "./src/sdk/redirects.ts",
|
|
27
|
+
"./sdk/sitemap": "./src/sdk/sitemap.ts",
|
|
28
|
+
"./sdk/useDevice": "./src/sdk/useDevice.ts",
|
|
29
|
+
"./middleware": "./src/middleware/index.ts",
|
|
30
|
+
"./middleware/healthMetrics": "./src/middleware/healthMetrics.ts",
|
|
31
|
+
"./sdk/wrapCaughtErrors": "./src/sdk/wrapCaughtErrors.ts",
|
|
32
|
+
"./sdk/csp": "./src/sdk/csp.ts",
|
|
33
|
+
"./sdk/urlUtils": "./src/sdk/urlUtils.ts",
|
|
34
|
+
"./sdk/mergeCacheControl": "./src/sdk/mergeCacheControl.ts",
|
|
35
|
+
"./sdk/requestContext": "./src/sdk/requestContext.ts",
|
|
36
|
+
"./sdk/createInvoke": "./src/sdk/createInvoke.ts",
|
|
37
|
+
"./matchers/posthog": "./src/matchers/posthog.ts",
|
|
38
|
+
"./matchers/builtins": "./src/matchers/builtins.ts",
|
|
39
|
+
"./types/widgets": "./src/types/widgets.ts",
|
|
40
|
+
"./routes": "./src/routes/index.ts",
|
|
41
|
+
"./scripts/generate-blocks": "./scripts/generate-blocks.ts",
|
|
42
|
+
"./scripts/generate-schema": "./scripts/generate-schema.ts",
|
|
43
|
+
"./scripts/generate-invoke": "./scripts/generate-invoke.ts"
|
|
44
|
+
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "tsc",
|
|
47
|
+
"typecheck": "tsc --noEmit",
|
|
48
|
+
"lint": "biome check src/ scripts/",
|
|
49
|
+
"lint:fix": "biome check --write src/ scripts/",
|
|
50
|
+
"lint:unused": "knip",
|
|
51
|
+
"format": "biome format src/ scripts/",
|
|
52
|
+
"format:fix": "biome format --write src/ scripts/",
|
|
53
|
+
"check": "npm run typecheck && npm run lint && npm run lint:unused"
|
|
54
|
+
},
|
|
55
|
+
"keywords": [
|
|
56
|
+
"deco",
|
|
57
|
+
"tanstack",
|
|
58
|
+
"cms",
|
|
59
|
+
"framework"
|
|
60
|
+
],
|
|
61
|
+
"author": "deco.cx",
|
|
62
|
+
"license": "MIT",
|
|
63
|
+
"repository": {
|
|
64
|
+
"type": "git",
|
|
65
|
+
"url": "https://github.com/decocms/deco-start.git"
|
|
66
|
+
},
|
|
67
|
+
"publishConfig": {
|
|
68
|
+
"registry": "https://registry.npmjs.org",
|
|
69
|
+
"access": "public"
|
|
70
|
+
},
|
|
71
|
+
"peerDependencies": {
|
|
72
|
+
"@tanstack/react-start": ">=1.0.0",
|
|
73
|
+
"@tanstack/store": ">=0.7.0",
|
|
74
|
+
"react": "^19.0.0",
|
|
75
|
+
"react-dom": "^19.0.0"
|
|
76
|
+
},
|
|
77
|
+
"devDependencies": {
|
|
78
|
+
"@biomejs/biome": "^2.4.6",
|
|
79
|
+
"@semantic-release/git": "^10.0.1",
|
|
80
|
+
"@tanstack/store": "^0.9.1",
|
|
81
|
+
"@types/react": "^19.0.0",
|
|
82
|
+
"@types/react-dom": "^19.0.0",
|
|
83
|
+
"knip": "^5.86.0",
|
|
84
|
+
"ts-morph": "^27.0.0",
|
|
85
|
+
"typescript": "^5.9.0"
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* Reads .deco/blocks/*.json and emits a TypeScript barrel.
|
|
4
|
+
*
|
|
5
|
+
* Usage (from site root):
|
|
6
|
+
* npx tsx node_modules/@decocms/start/scripts/generate-blocks.ts
|
|
7
|
+
*
|
|
8
|
+
* Env / CLI:
|
|
9
|
+
* --blocks-dir override input (default: .deco/blocks)
|
|
10
|
+
* --out-file override output (default: src/server/cms/blocks.gen.ts)
|
|
11
|
+
*/
|
|
12
|
+
import fs from "node:fs";
|
|
13
|
+
import path from "node:path";
|
|
14
|
+
|
|
15
|
+
const args = process.argv.slice(2);
|
|
16
|
+
function arg(name: string, fallback: string): string {
|
|
17
|
+
const idx = args.indexOf(`--${name}`);
|
|
18
|
+
return idx !== -1 && args[idx + 1] ? args[idx + 1] : fallback;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const blocksDir = path.resolve(process.cwd(), arg("blocks-dir", ".deco/blocks"));
|
|
22
|
+
const outFile = path.resolve(process.cwd(), arg("out-file", "src/server/cms/blocks.gen.ts"));
|
|
23
|
+
|
|
24
|
+
function decodeBlockName(filename: string): string {
|
|
25
|
+
return decodeURIComponent(decodeURIComponent(filename)).replace(/\.json$/, "");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (!fs.existsSync(blocksDir)) {
|
|
29
|
+
console.warn(`Blocks directory not found: ${blocksDir} — generating empty barrel.`);
|
|
30
|
+
fs.mkdirSync(path.dirname(outFile), { recursive: true });
|
|
31
|
+
fs.writeFileSync(
|
|
32
|
+
outFile,
|
|
33
|
+
`// Auto-generated — no blocks found\nexport const blocks: Record<string, any> = {};\n`,
|
|
34
|
+
);
|
|
35
|
+
process.exit(0);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const files = fs.readdirSync(blocksDir).filter((f) => f.endsWith(".json"));
|
|
39
|
+
|
|
40
|
+
// Deduplicate: prefer the non-URL-encoded filename when both exist
|
|
41
|
+
const blockFiles: Record<string, string> = {};
|
|
42
|
+
for (const file of files) {
|
|
43
|
+
const name = decodeBlockName(file);
|
|
44
|
+
const isEncoded = file !== `${name}.json`;
|
|
45
|
+
if (blockFiles[name] && !isEncoded) {
|
|
46
|
+
// Plain filename wins over URL-encoded variant
|
|
47
|
+
} else if (blockFiles[name] && isEncoded) {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
blockFiles[name] = file;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const blocks: Record<string, unknown> = {};
|
|
54
|
+
for (const [name, file] of Object.entries(blockFiles)) {
|
|
55
|
+
try {
|
|
56
|
+
const content = fs.readFileSync(path.join(blocksDir, file), "utf-8");
|
|
57
|
+
blocks[name] = JSON.parse(content);
|
|
58
|
+
} catch (e) {
|
|
59
|
+
console.warn(`Failed to parse ${file}:`, e);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const output = `// Auto-generated from .deco/blocks/*.json\n// Do not edit manually.\n\nexport const blocks: Record<string, any> = ${JSON.stringify(blocks, null, 2)};\n`;
|
|
64
|
+
|
|
65
|
+
fs.mkdirSync(path.dirname(outFile), { recursive: true });
|
|
66
|
+
fs.writeFileSync(outFile, output);
|
|
67
|
+
console.log(
|
|
68
|
+
`Generated ${Object.keys(blocks).length} blocks → ${path.relative(process.cwd(), outFile)}`,
|
|
69
|
+
);
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* Scans @decocms/apps vtex/invoke.ts and generates a site-local invoke file
|
|
4
|
+
* with top-level createServerFn declarations.
|
|
5
|
+
*
|
|
6
|
+
* TanStack Start's compiler only transforms createServerFn().handler() when
|
|
7
|
+
* the call is at module top-level (assigned to a const). The factory pattern
|
|
8
|
+
* used in @decocms/apps/vtex/invoke.ts causes the "fast path" in the compiler
|
|
9
|
+
* to skip the .handler() calls because they're inside a function body.
|
|
10
|
+
*
|
|
11
|
+
* This script generates an equivalent file where each server function is a
|
|
12
|
+
* top-level const, which the compiler can correctly transform into RPC stubs.
|
|
13
|
+
*
|
|
14
|
+
* Usage (from site root):
|
|
15
|
+
* npx tsx node_modules/@decocms/start/scripts/generate-invoke.ts
|
|
16
|
+
*
|
|
17
|
+
* Env / CLI:
|
|
18
|
+
* --out-file override output (default: src/server/invoke.gen.ts)
|
|
19
|
+
* --apps-dir override @decocms/apps location (default: auto-resolve from node_modules)
|
|
20
|
+
*/
|
|
21
|
+
import fs from "node:fs";
|
|
22
|
+
import path from "node:path";
|
|
23
|
+
import { Project, type PropertyAssignment, SyntaxKind } from "ts-morph";
|
|
24
|
+
|
|
25
|
+
const args = process.argv.slice(2);
|
|
26
|
+
function arg(name: string, fallback: string): string {
|
|
27
|
+
const idx = args.indexOf(`--${name}`);
|
|
28
|
+
return idx !== -1 && args[idx + 1] ? args[idx + 1] : fallback;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const cwd = process.cwd();
|
|
32
|
+
const outFile = path.resolve(cwd, arg("out-file", "src/server/invoke.gen.ts"));
|
|
33
|
+
|
|
34
|
+
function resolveAppsDir(): string {
|
|
35
|
+
const explicit = arg("apps-dir", "");
|
|
36
|
+
if (explicit) return path.resolve(cwd, explicit);
|
|
37
|
+
|
|
38
|
+
// Try common locations
|
|
39
|
+
const candidates = [
|
|
40
|
+
path.resolve(cwd, "node_modules/@decocms/apps"),
|
|
41
|
+
path.resolve(cwd, "../apps-start"),
|
|
42
|
+
];
|
|
43
|
+
for (const c of candidates) {
|
|
44
|
+
if (fs.existsSync(path.join(c, "vtex/invoke.ts"))) return c;
|
|
45
|
+
}
|
|
46
|
+
throw new Error("Could not find @decocms/apps. Use --apps-dir to specify its location.");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const appsDir = resolveAppsDir();
|
|
50
|
+
const invokeFile = path.join(appsDir, "vtex/invoke.ts");
|
|
51
|
+
|
|
52
|
+
if (!fs.existsSync(invokeFile)) {
|
|
53
|
+
console.error(`invoke.ts not found at: ${invokeFile}`);
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
// Parse the source invoke.ts to extract action definitions
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
|
|
61
|
+
interface ActionDef {
|
|
62
|
+
name: string;
|
|
63
|
+
/** The import source for the action function (e.g., "@decocms/apps/vtex/actions/checkout") */
|
|
64
|
+
importSource: string;
|
|
65
|
+
/** The imported function name (e.g., "addItemsToCart") */
|
|
66
|
+
importedFn: string;
|
|
67
|
+
/** The input type as a string (e.g., "{ orderFormId: string; ... }") */
|
|
68
|
+
inputType: string;
|
|
69
|
+
/** The return type as a string (e.g., "OrderForm") */
|
|
70
|
+
returnType: string;
|
|
71
|
+
/** Whether to unwrap VtexFetchResult */
|
|
72
|
+
unwrap: boolean;
|
|
73
|
+
/** The body of the action call (e.g., "addItemsToCart(input.orderFormId, input.orderItems)") */
|
|
74
|
+
callBody: string;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const project = new Project({ compilerOptions: { strict: true } });
|
|
78
|
+
const sourceFile = project.addSourceFileAtPath(invokeFile);
|
|
79
|
+
|
|
80
|
+
// Collect all imports to know which functions come from where
|
|
81
|
+
const importMap = new Map<string, { source: string; importedName: string }>();
|
|
82
|
+
for (const imp of sourceFile.getImportDeclarations()) {
|
|
83
|
+
const source = imp.getModuleSpecifierValue();
|
|
84
|
+
for (const named of imp.getNamedImports()) {
|
|
85
|
+
const localName = named.getName();
|
|
86
|
+
const importedName = named.getAliasNode()?.getText() || localName;
|
|
87
|
+
importMap.set(localName, {
|
|
88
|
+
source: source.startsWith("./") ? `@decocms/apps/vtex/${source.slice(2)}` : source,
|
|
89
|
+
importedName: localName,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Collect type imports
|
|
95
|
+
const typeImportMap = new Map<string, { source: string; importedName: string }>();
|
|
96
|
+
for (const imp of sourceFile.getImportDeclarations()) {
|
|
97
|
+
if (!imp.isTypeOnly()) {
|
|
98
|
+
for (const named of imp.getNamedImports()) {
|
|
99
|
+
if (named.isTypeOnly()) {
|
|
100
|
+
const localName = named.getName();
|
|
101
|
+
const source = imp.getModuleSpecifierValue();
|
|
102
|
+
typeImportMap.set(localName, {
|
|
103
|
+
source: source.startsWith("./") ? `@decocms/apps/vtex/${source.slice(2)}` : source,
|
|
104
|
+
importedName: localName,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (imp.isTypeOnly()) {
|
|
110
|
+
const source = imp.getModuleSpecifierValue();
|
|
111
|
+
for (const named of imp.getNamedImports()) {
|
|
112
|
+
const localName = named.getName();
|
|
113
|
+
typeImportMap.set(localName, {
|
|
114
|
+
source: source.startsWith("./") ? `@decocms/apps/vtex/${source.slice(2)}` : source,
|
|
115
|
+
importedName: localName,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Find the invoke const and extract actions
|
|
122
|
+
const invokeVar = sourceFile.getVariableDeclaration("invoke");
|
|
123
|
+
if (!invokeVar) {
|
|
124
|
+
console.error("Could not find 'export const invoke' in invoke.ts");
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const actions: ActionDef[] = [];
|
|
129
|
+
const invokeInit = invokeVar.getInitializer();
|
|
130
|
+
if (!invokeInit) {
|
|
131
|
+
console.error("invoke variable has no initializer");
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Navigate: invoke → .vtex → .actions → each property
|
|
136
|
+
const vtexProp = invokeInit
|
|
137
|
+
.asKindOrThrow(SyntaxKind.AsExpression)
|
|
138
|
+
.getExpression()
|
|
139
|
+
.asKindOrThrow(SyntaxKind.ObjectLiteralExpression)
|
|
140
|
+
.getProperty("vtex");
|
|
141
|
+
|
|
142
|
+
if (!vtexProp) {
|
|
143
|
+
console.error("Could not find 'vtex' property in invoke object");
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const vtexObj = (vtexProp as PropertyAssignment)
|
|
148
|
+
.getInitializer()!
|
|
149
|
+
.asKindOrThrow(SyntaxKind.ObjectLiteralExpression);
|
|
150
|
+
|
|
151
|
+
const actionsProp = vtexObj.getProperty("actions");
|
|
152
|
+
if (!actionsProp) {
|
|
153
|
+
console.error("Could not find 'actions' property in vtex object");
|
|
154
|
+
process.exit(1);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const actionsObj = (actionsProp as PropertyAssignment)
|
|
158
|
+
.getInitializer()!
|
|
159
|
+
.asKindOrThrow(SyntaxKind.ObjectLiteralExpression);
|
|
160
|
+
|
|
161
|
+
for (const prop of actionsObj.getProperties()) {
|
|
162
|
+
if (prop.getKind() !== SyntaxKind.PropertyAssignment) continue;
|
|
163
|
+
const pa = prop as PropertyAssignment;
|
|
164
|
+
const name = pa.getName();
|
|
165
|
+
const initText = pa.getInitializer()!.getText();
|
|
166
|
+
|
|
167
|
+
// Check if it uses createInvokeFn with unwrap
|
|
168
|
+
const unwrap = initText.includes("unwrap: true");
|
|
169
|
+
|
|
170
|
+
// Extract the arrow function body from createInvokeFn((input: ...) => ...)
|
|
171
|
+
// We'll parse the call expression to get the action call
|
|
172
|
+
const callExpr = pa.getInitializer()!;
|
|
173
|
+
let inputType = "any";
|
|
174
|
+
let callBody = "";
|
|
175
|
+
|
|
176
|
+
// Navigate through potential type assertion (as ...)
|
|
177
|
+
let createInvokeFnCall = callExpr;
|
|
178
|
+
if (callExpr.getKind() === SyntaxKind.AsExpression) {
|
|
179
|
+
createInvokeFnCall = callExpr.asKindOrThrow(SyntaxKind.AsExpression).getExpression();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Now we have createInvokeFn(...) call
|
|
183
|
+
if (createInvokeFnCall.getKind() === SyntaxKind.CallExpression) {
|
|
184
|
+
const callArgs = createInvokeFnCall.asKindOrThrow(SyntaxKind.CallExpression).getArguments();
|
|
185
|
+
if (callArgs.length >= 1) {
|
|
186
|
+
const arrowFn = callArgs[0];
|
|
187
|
+
if (arrowFn.getKind() === SyntaxKind.ArrowFunction) {
|
|
188
|
+
const arrow = arrowFn.asKindOrThrow(SyntaxKind.ArrowFunction);
|
|
189
|
+
const params = arrow.getParameters();
|
|
190
|
+
if (params.length >= 1) {
|
|
191
|
+
const paramType = params[0].getTypeNode()?.getText() || "any";
|
|
192
|
+
inputType = paramType;
|
|
193
|
+
}
|
|
194
|
+
// Get the body (the actual action call)
|
|
195
|
+
const body = arrow.getBody();
|
|
196
|
+
callBody = body.getText();
|
|
197
|
+
|
|
198
|
+
// If body is a block, extract the expression
|
|
199
|
+
if (callBody.startsWith("{")) {
|
|
200
|
+
// It's a block body — skip for now, use simplified version
|
|
201
|
+
callBody = "";
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Determine which function is being called
|
|
208
|
+
let importedFn = "";
|
|
209
|
+
let importSource = "";
|
|
210
|
+
for (const [fnName, info] of importMap.entries()) {
|
|
211
|
+
if (callBody.includes(`${fnName}(`)) {
|
|
212
|
+
importedFn = fnName;
|
|
213
|
+
importSource = info.source;
|
|
214
|
+
break;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Extract the return type from the "as" assertion if present
|
|
219
|
+
let returnType = "any";
|
|
220
|
+
if (callExpr.getKind() === SyntaxKind.AsExpression) {
|
|
221
|
+
const asExpr = callExpr.asKindOrThrow(SyntaxKind.AsExpression);
|
|
222
|
+
const typeText = asExpr.getTypeNode()?.getText() || "";
|
|
223
|
+
// Extract Promise<X> from (ctx: {...}) => Promise<X>
|
|
224
|
+
const promiseMatch = typeText.match(/Promise<(.+)>$/s);
|
|
225
|
+
if (promiseMatch) {
|
|
226
|
+
returnType = promiseMatch[1].trim();
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
actions.push({
|
|
231
|
+
name,
|
|
232
|
+
importSource,
|
|
233
|
+
importedFn,
|
|
234
|
+
inputType,
|
|
235
|
+
returnType,
|
|
236
|
+
unwrap,
|
|
237
|
+
callBody,
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// ---------------------------------------------------------------------------
|
|
242
|
+
// Generate the output file
|
|
243
|
+
// ---------------------------------------------------------------------------
|
|
244
|
+
|
|
245
|
+
// Collect unique imports needed
|
|
246
|
+
const fnImports = new Map<string, Set<string>>();
|
|
247
|
+
const typeImports = new Map<string, Set<string>>();
|
|
248
|
+
|
|
249
|
+
for (const action of actions) {
|
|
250
|
+
if (action.importSource && action.importedFn) {
|
|
251
|
+
if (!fnImports.has(action.importSource)) {
|
|
252
|
+
fnImports.set(action.importSource, new Set());
|
|
253
|
+
}
|
|
254
|
+
fnImports.get(action.importSource)!.add(action.importedFn);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Add type imports referenced in inputType or returnType
|
|
259
|
+
for (const action of actions) {
|
|
260
|
+
const allText = action.inputType + action.returnType + action.callBody;
|
|
261
|
+
for (const [typeName, info] of typeImportMap.entries()) {
|
|
262
|
+
if (allText.includes(typeName)) {
|
|
263
|
+
if (!typeImports.has(info.source)) {
|
|
264
|
+
typeImports.set(info.source, new Set());
|
|
265
|
+
}
|
|
266
|
+
typeImports.get(info.source)!.add(typeName);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
// Also check value imports that appear in the types (like SimulationItem)
|
|
270
|
+
for (const [fnName, info] of importMap.entries()) {
|
|
271
|
+
if (action.inputType.includes(fnName) && !fnImports.get(info.source)?.has(fnName)) {
|
|
272
|
+
if (!typeImports.has(info.source)) {
|
|
273
|
+
typeImports.set(info.source, new Set());
|
|
274
|
+
}
|
|
275
|
+
typeImports.get(info.source)!.add(fnName);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Build output
|
|
281
|
+
let out = `// Auto-generated by @decocms/start/scripts/generate-invoke.ts
|
|
282
|
+
// Do not edit manually. Re-run the generator to update.
|
|
283
|
+
//
|
|
284
|
+
// Each server function is a top-level const so TanStack Start's compiler
|
|
285
|
+
// can transform createServerFn().handler() into RPC stubs on the client.
|
|
286
|
+
import { createServerFn } from "@tanstack/react-start";
|
|
287
|
+
`;
|
|
288
|
+
|
|
289
|
+
// Add function imports
|
|
290
|
+
for (const [source, fns] of fnImports) {
|
|
291
|
+
out += `import { ${[...fns].join(", ")} } from "${source}";\n`;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Add type imports
|
|
295
|
+
for (const [source, types] of typeImports) {
|
|
296
|
+
// Don't duplicate if already imported as value
|
|
297
|
+
const valueImports = fnImports.get(source);
|
|
298
|
+
const onlyTypes = [...types].filter((t) => !valueImports?.has(t));
|
|
299
|
+
if (onlyTypes.length > 0) {
|
|
300
|
+
out += `import type { ${onlyTypes.join(", ")} } from "${source}";\n`;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
out += `
|
|
305
|
+
function unwrapResult<T>(result: unknown): T {
|
|
306
|
+
if (result && typeof result === "object" && "data" in result) {
|
|
307
|
+
return (result as { data: T }).data;
|
|
308
|
+
}
|
|
309
|
+
return result as T;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// ---------------------------------------------------------------------------
|
|
313
|
+
// Top-level server function declarations
|
|
314
|
+
// ---------------------------------------------------------------------------
|
|
315
|
+
`;
|
|
316
|
+
|
|
317
|
+
for (const action of actions) {
|
|
318
|
+
const varName = `$${action.name}`;
|
|
319
|
+
|
|
320
|
+
if (action.callBody && action.importedFn) {
|
|
321
|
+
// Replace "input" references with "ctx.data" in the call body
|
|
322
|
+
let body = action.callBody;
|
|
323
|
+
// The callBody looks like: functionName(input.foo, input.bar)
|
|
324
|
+
// We need it to be: functionName(ctx.data.foo, ctx.data.bar)
|
|
325
|
+
body = body.replace(/\binput\./g, "ctx.data.");
|
|
326
|
+
// Handle cases like functionName(input) without dot
|
|
327
|
+
body = body.replace(/\binput\b(?!\.)/g, "ctx.data");
|
|
328
|
+
|
|
329
|
+
if (action.unwrap) {
|
|
330
|
+
out += `\nconst ${varName} = createServerFn({ method: "POST" })
|
|
331
|
+
.handler(async (ctx: { data: ${action.inputType} }) => {
|
|
332
|
+
const result = await ${body};
|
|
333
|
+
return unwrapResult(result);
|
|
334
|
+
});\n`;
|
|
335
|
+
} else {
|
|
336
|
+
out += `\nconst ${varName} = createServerFn({ method: "POST" })
|
|
337
|
+
.handler(async (ctx: { data: ${action.inputType} }) => {
|
|
338
|
+
return await ${body};
|
|
339
|
+
});\n`;
|
|
340
|
+
}
|
|
341
|
+
} else {
|
|
342
|
+
// Fallback: couldn't parse — generate a stub
|
|
343
|
+
out += `\n// TODO: could not auto-generate ${action.name} — add manually\nconst ${varName} = createServerFn({ method: "POST" })
|
|
344
|
+
.handler(async (ctx: { data: any }) => {
|
|
345
|
+
throw new Error("${action.name}: not implemented — regenerate invoke");
|
|
346
|
+
});\n`;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Generate the invoke object
|
|
351
|
+
out += `
|
|
352
|
+
// ---------------------------------------------------------------------------
|
|
353
|
+
// Public invoke object — same DX as @decocms/apps/vtex/invoke
|
|
354
|
+
// ---------------------------------------------------------------------------
|
|
355
|
+
|
|
356
|
+
export const invoke = {
|
|
357
|
+
vtex: {
|
|
358
|
+
actions: {
|
|
359
|
+
`;
|
|
360
|
+
|
|
361
|
+
for (const action of actions) {
|
|
362
|
+
const varName = `$${action.name}`;
|
|
363
|
+
if (action.returnType !== "any") {
|
|
364
|
+
out += ` ${action.name}: ${varName} as unknown as (ctx: { data: ${action.inputType} }) => Promise<${action.returnType}>,\n`;
|
|
365
|
+
} else {
|
|
366
|
+
out += ` ${action.name}: ${varName},\n`;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
out += ` },
|
|
371
|
+
},
|
|
372
|
+
} as const;
|
|
373
|
+
`;
|
|
374
|
+
|
|
375
|
+
// Write output
|
|
376
|
+
fs.mkdirSync(path.dirname(outFile), { recursive: true });
|
|
377
|
+
fs.writeFileSync(outFile, out);
|
|
378
|
+
console.log(`Generated ${actions.length} server functions → ${path.relative(cwd, outFile)}`);
|