@wix/agent-skills 1.6.0 → 1.8.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/README.md +5 -9
- package/package.json +1 -1
- package/skills/wix-app/SKILL.md +51 -42
- package/skills/wix-app/references/BACKEND_EVENT.md +10 -65
- package/skills/wix-app/references/CUSTOM_ELEMENT_WIDGET.md +101 -406
- package/skills/wix-app/references/DASHBOARD_MENU_PLUGIN.md +14 -111
- package/skills/wix-app/references/DASHBOARD_MODAL.md +56 -205
- package/skills/wix-app/references/DASHBOARD_PAGE.md +19 -98
- package/skills/wix-app/references/DASHBOARD_PLUGIN.md +6 -108
- package/skills/wix-app/references/DATA_COLLECTION.md +113 -198
- package/skills/wix-app/references/EMBEDDED_SCRIPT.md +41 -268
- package/skills/wix-app/references/EXTENSION_REGISTRATION.md +27 -76
- package/skills/wix-app/references/SERVICE_PLUGIN.md +35 -188
- package/skills/wix-app/references/SITE_PLUGIN.md +8 -92
- package/skills/wix-app/references/STORES_VERSIONING.md +1 -0
- package/skills/wix-app/references/backend-event/COMMON-EVENTS.md +0 -44
- package/skills/wix-app/references/custom-element-widget/SETTINGS_PANEL.md +19 -330
- package/skills/wix-app/references/dashboard-menu-plugin/bookings-slots.md +0 -12
- package/skills/wix-app/references/dashboard-menu-plugin/ecommerce-slots.md +7 -0
- package/skills/wix-app/references/dashboard-menu-plugin/restaurants-slots.md +0 -12
- package/skills/wix-app/references/dashboard-plugin/SLOTS.md +3 -15
- package/skills/wix-app/references/service-plugin/ADDITIONAL-FEES.md +2 -18
- package/skills/wix-app/references/service-plugin/BOOKINGS-STAFF-SORTING.md +2 -42
- package/skills/wix-app/references/service-plugin/DISCOUNT-TRIGGERS.md +1 -11
- package/skills/wix-app/references/service-plugin/GIFT-CARDS.md +1 -11
- package/skills/wix-app/references/service-plugin/PAYMENT-SETTINGS.md +2 -40
- package/skills/wix-app/references/service-plugin/SHIPPING-RATES.md +2 -19
- package/skills/wix-app/references/service-plugin/VALIDATIONS.md +2 -18
- package/skills/wix-app/references/site-plugin/SLOTS.md +17 -68
- package/skills/wix-design-system/SKILL.md +4 -0
- package/skills/wix-headless/SKILL.md +131 -386
- package/skills/wix-headless/references/BUILD-astro.md +246 -0
- package/skills/wix-headless/references/BUILD-own-build.md +167 -0
- package/skills/wix-headless/references/BUILD.md +105 -0
- package/skills/wix-headless/references/DESIGN_SYSTEM.md +175 -0
- package/skills/wix-headless/references/DISCOVERY-connect.md +55 -0
- package/skills/wix-headless/references/DISCOVERY-create.md +238 -0
- package/skills/wix-headless/references/DISCOVERY.md +50 -306
- package/skills/wix-headless/references/PLAN-connect.md +19 -0
- package/skills/wix-headless/references/PLAN-create.md +17 -0
- package/skills/wix-headless/references/PLAN.md +92 -0
- package/skills/wix-headless/references/SEED.md +214 -0
- package/skills/wix-headless/references/SETUP.md +73 -248
- package/skills/wix-headless/references/{blog → astro/blog}/BLOG_PAGES.md +6 -5
- package/skills/wix-headless/references/{blog → astro/blog}/BLOG_SETUP.md +2 -2
- package/skills/wix-headless/references/astro/bookings/COMPONENTS.md +337 -0
- package/skills/wix-headless/references/astro/bookings/SERVICES_PAGES.md +428 -0
- package/skills/wix-headless/references/astro/cms/CMS_FOUNDATIONS.md +214 -0
- package/skills/wix-headless/references/{cms → astro/cms}/FAQ_KNOWLEDGE_BASE.md +14 -12
- package/skills/wix-headless/references/{cms → astro/cms}/PORTFOLIO.md +15 -16
- package/skills/wix-headless/references/{cms → astro/cms}/RESOURCE_LIBRARY.md +13 -15
- package/skills/wix-headless/references/{cms → astro/cms}/TEAM_DIRECTORY.md +11 -11
- package/skills/wix-headless/references/astro/designer/INSTRUCTIONS.md +354 -0
- package/skills/wix-headless/references/{ecom → astro/ecom}/CART_PAGES.md +8 -9
- package/skills/wix-headless/references/{ecom → astro/ecom}/CART_WIRING.md +6 -6
- package/skills/wix-headless/references/{ecom → astro/ecom}/COMPONENTS_CSS.md +6 -6
- package/skills/wix-headless/references/{forms → astro/forms}/CONTACT_FORM.md +18 -32
- package/skills/wix-headless/references/{gift-cards → astro/gift-cards}/COMPONENTS.md +3 -3
- package/skills/wix-headless/references/{gift-cards → astro/gift-cards}/PAGES.md +2 -2
- package/skills/wix-headless/references/{stores → astro/stores}/BACK_IN_STOCK.md +7 -7
- package/skills/wix-headless/references/{stores → astro/stores}/CATEGORY_PAGES.md +12 -13
- package/skills/wix-headless/references/{stores → astro/stores}/COMPONENTS_CSS.md +7 -7
- package/skills/wix-headless/references/{stores → astro/stores}/HOME_AND_NAV.md +11 -9
- package/skills/wix-headless/references/{stores → astro/stores}/PRODUCT_PAGES.md +11 -9
- package/skills/wix-headless/references/{stores → astro/stores}/SHARED_WIRING.md +8 -7
- package/skills/wix-headless/references/astro/templates/Footer.astro +27 -0
- package/skills/wix-headless/references/astro/templates/Layout.astro +132 -0
- package/skills/wix-headless/references/astro/templates/Navigation.astro +38 -0
- package/skills/wix-headless/references/astro/templates/astro.config.mjs +41 -0
- package/skills/wix-headless/references/astro/templates/bookings/AvailabilityCalendar.tsx +218 -0
- package/skills/wix-headless/references/astro/templates/bookings/BookingForm.tsx +194 -0
- package/skills/wix-headless/references/astro/templates/bookings/ManageBooking.tsx +84 -0
- package/skills/wix-headless/references/astro/templates/bookings/SeoTags.astro +25 -0
- package/skills/wix-headless/references/astro/templates/bookings/ServiceBookingFlow.tsx +50 -0
- package/skills/wix-headless/references/astro/templates/bookings/ServiceCard.astro +45 -0
- package/skills/wix-headless/references/astro/templates/bookings/api/confirm-booking.ts +34 -0
- package/skills/wix-headless/references/astro/templates/bookings/api/waitlist.ts +74 -0
- package/skills/wix-headless/references/astro/templates/bookings/booking-confirmation.astro +111 -0
- package/skills/wix-headless/references/astro/templates/bookings/components-bookings.css +413 -0
- package/skills/wix-headless/references/astro/templates/bookings/manage-booking.astro +76 -0
- package/skills/wix-headless/references/astro/templates/bookings/services/[slug].astro +81 -0
- package/skills/wix-headless/references/astro/templates/bookings/services/index.astro +65 -0
- package/skills/wix-headless/references/astro/templates/ecom/components-ecom.css +302 -0
- package/skills/wix-headless/references/astro/templates/global.css +286 -0
- package/skills/wix-headless/references/astro/templates/index.astro +56 -0
- package/skills/wix-headless/{templates → references/astro/templates}/stores/back-in-stock.ts +1 -1
- package/skills/wix-headless/{templates → references/astro/templates}/stores/components-stores.css +138 -33
- package/skills/wix-headless/references/blog/BLOG_CONTENT.md +28 -19
- package/skills/wix-headless/references/blog/INSTRUCTIONS.md +6 -6
- package/skills/wix-headless/references/bookings/INSTRUCTIONS.md +106 -0
- package/skills/wix-headless/references/bookings/SERVICES_DATA.md +295 -0
- package/skills/wix-headless/references/cms/CMS_FOUNDATIONS.md +35 -232
- package/skills/wix-headless/references/cms/INSTRUCTIONS.md +39 -8
- package/skills/wix-headless/references/commands/known-apps.json +2 -1
- package/skills/wix-headless/references/custom/CONNECTION_PLAN.md +142 -0
- package/skills/wix-headless/references/custom/INSTRUCTIONS.md +88 -0
- package/skills/wix-headless/references/custom/blog/WIRING.md +52 -0
- package/skills/wix-headless/references/custom/bookings/WIRING.md +88 -0
- package/skills/wix-headless/references/custom/cms/WIRING.md +97 -0
- package/skills/wix-headless/references/custom/ecom/WIRING.md +67 -0
- package/skills/wix-headless/references/custom/forms/WIRING.md +101 -0
- package/skills/wix-headless/references/custom/stores/WIRING.md +80 -0
- package/skills/wix-headless/references/ecom/INSTRUCTIONS.md +12 -12
- package/skills/wix-headless/references/forms/INSTRUCTIONS.md +6 -6
- package/skills/wix-headless/references/gift-cards/INSTRUCTIONS.md +13 -13
- package/skills/wix-headless/references/images/INSTRUCTIONS.md +58 -60
- package/skills/wix-headless/references/seed-recipes.md +49 -0
- package/skills/wix-headless/references/shared/AGENTS_MD.md +47 -0
- package/skills/wix-headless/references/shared/AUTHENTICATION.md +108 -0
- package/skills/wix-headless/references/shared/BUILD_NOISE.md +2 -2
- package/skills/wix-headless/references/shared/DESIGN_MD.md +85 -0
- package/skills/wix-headless/references/shared/DOCS_SEARCH.md +41 -0
- package/skills/wix-headless/references/shared/IMAGE_GENERATION.md +40 -39
- package/skills/wix-headless/references/shared/IMPLEMENTER.md +92 -41
- package/skills/wix-headless/references/shared/PRODUCTION_SHARP_EDGES.md +3 -3
- package/skills/wix-headless/references/shared/RETURN_CONTRACT.md +32 -312
- package/skills/wix-headless/references/shared/STYLING.md +48 -21
- package/skills/wix-headless/references/stores/INSTRUCTIONS.md +59 -29
- package/skills/wix-headless/references/stores/PRODUCT_CATALOG_DATA.md +31 -28
- package/skills/wix-headless/references/verticals/_schema.json +31 -117
- package/skills/wix-headless/references/verticals/_schema.md +60 -150
- package/skills/wix-headless/references/verticals/blog.md +5 -99
- package/skills/wix-headless/references/verticals/bookings.md +41 -0
- package/skills/wix-headless/references/verticals/cms.md +9 -100
- package/skills/wix-headless/references/verticals/ecom.md +6 -77
- package/skills/wix-headless/references/verticals/forms.md +5 -85
- package/skills/wix-headless/references/verticals/gift-cards.md +6 -103
- package/skills/wix-headless/references/verticals/stores.md +10 -144
- package/skills/wix-headless/scripts/check-manifest.mjs +148 -11
- package/skills/wix-headless/scripts/compose.mjs +540 -0
- package/skills/wix-headless/scripts/emit-design-tokens.mjs +136 -53
- package/skills/wix-headless/scripts/flatten.sh +61 -0
- package/skills/wix-headless/scripts/generate-images.mjs +242 -0
- package/skills/wix-headless/scripts/patch-decorative-slots.mjs +67 -43
- package/skills/wix-headless/scripts/plan-entity-image-waves.mjs +3 -3
- package/skills/wix-headless/scripts/scaffold.sh +113 -27
- package/skills/wix-headless/scripts/seed-utilities.sh +50 -12
- package/skills/wix-headless/scripts/write-decorative-json.mjs +62 -0
- package/skills/wix-manage/SKILL.md +4 -8
- package/skills/wix-manage/references/blog/how-to-create-blog-posts.md +2 -0
- package/skills/wix-manage/references/cms/cms-data-items-crud.md +4 -0
- package/skills/wix-manage/references/cms/cms-references-and-relationships.md +2 -0
- package/skills/wix-manage/references/cms/cms-schema-management.md +4 -0
- package/skills/wix-manage/references/domains/domain-search-and-purchase.md +10 -7
- package/skills/wix-manage/references/ecommerce/api-shipping.md +443 -0
- package/skills/wix-manage/references/ecommerce/flow-add-free-shipping.md +5 -6
- package/skills/wix-manage/references/ecommerce/flow-fix-coverage-gaps.md +4 -5
- package/skills/wix-manage/references/ecommerce/flow-optimize-shipping-rates.md +4 -5
- package/skills/wix-manage/references/ecommerce/goal-increase-aov.md +1 -1
- package/skills/wix-manage/references/ecommerce/goal-reduce-cart-abandonment.md +1 -2
- package/skills/wix-manage/references/ecommerce/guardrail-shipping-health.md +2 -2
- package/skills/wix-manage/references/ecommerce/recipe-apply-shipping-recommendations.md +4 -5
- package/skills/wix-manage/references/ecommerce/recommend-ecommerce-strategy.md +7 -7
- package/skills/wix-manage/references/ecommerce/setup-discount-rules.md +209 -72
- package/skills/wix-manage/references/ecommerce/setup-store-pickup-location.md +9 -10
- package/skills/wix-manage/references/ecommerce/skill-graph.md +4 -4
- package/skills/wix-manage/references/forms/create-form.md +50 -0
- package/skills/wix-manage/references/stores/bulk-create-products-with-options.md +2 -0
- package/skills/wix-manage/references/stores/create-product-from-image.md +91 -38
- package/skills/wix-manage/references/stores/setup-online-store-catalog-v3.md +2 -0
- package/skills/wix-app/references/custom-element-widget/default-thumbnail.png +0 -0
- package/skills/wix-app/references/data-collection/extension-template.ts +0 -107
- package/skills/wix-headless/references/ORCHESTRATION.md +0 -269
- package/skills/wix-headless/references/commands/install-app.md +0 -42
- package/skills/wix-headless/references/commands/mcp-bootstrap.md +0 -28
- package/skills/wix-headless/references/designer/INSTRUCTIONS.md +0 -828
- package/skills/wix-headless/references/shared/MCP_PREFIX.md +0 -105
- package/skills/wix-headless/scripts/copy-utility-templates.mjs +0 -153
- package/skills/wix-headless/scripts/finalize-run-json.mjs +0 -141
- package/skills/wix-headless/scripts/init-site-json.mjs +0 -63
- package/skills/wix-headless/scripts/preview.sh +0 -45
- package/skills/wix-headless/scripts/release.sh +0 -73
- package/skills/wix-headless/templates/ecom/components-ecom.css +0 -215
- package/skills/wix-manage/references/events/list-events.md +0 -203
- /package/skills/wix-headless/{templates → references/astro/templates}/ecom/CartBadge.tsx +0 -0
- /package/skills/wix-headless/{templates → references/astro/templates}/ecom/CartView.tsx +0 -0
- /package/skills/wix-headless/{templates → references/astro/templates}/ecom/cart.astro +0 -0
- /package/skills/wix-headless/{templates → references/astro/templates}/ecom/discounts.ts +0 -0
- /package/skills/wix-headless/{templates → references/astro/templates}/ecom/thank-you.astro +0 -0
- /package/skills/wix-headless/{templates → references/astro/templates}/gift-cards/GiftCardPurchase.tsx +0 -0
- /package/skills/wix-headless/{templates → references/astro/templates}/gift-cards/_home-teaser-snippet.astro +0 -0
- /package/skills/wix-headless/{templates → references/astro/templates}/gift-cards/_nav-snippet.astro +0 -0
- /package/skills/wix-headless/{templates → references/astro/templates}/gift-cards/components-gift-cards.css +0 -0
- /package/skills/wix-headless/{templates → references/astro/templates}/gift-cards/gift-cards.astro +0 -0
- /package/skills/wix-headless/{templates → references/astro/templates}/gift-cards/gift-cards.ts +0 -0
- /package/skills/wix-headless/{templates → references/astro/templates}/stores/AddToCartButton.tsx +0 -0
- /package/skills/wix-headless/{templates → references/astro/templates}/stores/BackInStockForm.tsx +0 -0
- /package/skills/wix-headless/{templates → references/astro/templates}/stores/CategoryRail.astro +0 -0
- /package/skills/wix-headless/{templates → references/astro/templates}/stores/ProductCard.astro +0 -0
- /package/skills/wix-headless/{templates → references/astro/templates}/stores/ProductPurchase.tsx +0 -0
- /package/skills/wix-headless/{templates → references/astro/templates}/stores/SeoTags.astro +0 -0
- /package/skills/wix-headless/{templates → references/astro/templates}/stores/categories.ts +0 -0
- /package/skills/wix-headless/{templates → references/astro/templates}/stores/category/[slug].astro +0 -0
- /package/skills/wix-headless/{templates → references/astro/templates}/stores/products/[slug].astro +0 -0
- /package/skills/wix-headless/{templates → references/astro/templates}/stores/products/index.astro +0 -0
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// ── compose.mjs — the deterministic Composer (astro design-system phase) ──────
|
|
3
|
+
//
|
|
4
|
+
// Replaces the former `design-system-composer` LLM subagent (this script is now
|
|
5
|
+
// the sole spec). The Composer's job is MECHANICAL: take the Designer's framework-
|
|
6
|
+
// agnostic spec + the application inputs and SUBSTITUTE them into six pinned
|
|
7
|
+
// skeletons at references/astro/templates/. There is no judgment in re-emitting
|
|
8
|
+
// the fixed bulk (View-Transitions script, @utility btn family, view-transition
|
|
9
|
+
// CSS), so an LLM is pure cost + run-to-run variance. This script does it in
|
|
10
|
+
// sub-second, byte-reproducibly.
|
|
11
|
+
//
|
|
12
|
+
// It is a sibling of emit-design-tokens.mjs / patch-decorative-slots.mjs and,
|
|
13
|
+
// like them, reads its input as a JSON object on stdin and takes the project
|
|
14
|
+
// dir as argv[2]. It locates the skeletons relative to its own path (no
|
|
15
|
+
// SKILL_ROOT needed).
|
|
16
|
+
//
|
|
17
|
+
// Usage:
|
|
18
|
+
// node compose.mjs <project-dir> <<'JSON'
|
|
19
|
+
// { "shell": {...}, "brand": {...}, "navLinks": [...],
|
|
20
|
+
// "loadedPacks": [...], "packsWithComponents": [...], "disabledPacks": [...] }
|
|
21
|
+
// JSON
|
|
22
|
+
//
|
|
23
|
+
// Tokens come from DESIGN.md — the single design format (no inline token JSON).
|
|
24
|
+
// compose reads `<project-dir>/DESIGN.md` (or `designMdPath`), parses its
|
|
25
|
+
// FRONTMATTER ONLY (the markdown body is documentation, never read), and applies
|
|
26
|
+
// a role-translation table (references/shared/DESIGN_MD.md) so a DESIGN.md
|
|
27
|
+
// authored with standard roles (primary/surface/on-surface/…) maps onto the wix
|
|
28
|
+
// `--color-*` vocabulary; wix-native keys pass through losslessly. `rounded` →
|
|
29
|
+
// radii; a custom `containers` group + a `googleFontsHref` key are honored.
|
|
30
|
+
// The Designer authors that DESIGN.md directly; emit-design-tokens.mjs projects
|
|
31
|
+
// the token CSS/types from it. DESIGN.md exists on disk before compose runs.
|
|
32
|
+
//
|
|
33
|
+
// stdin JSON (the application inputs — NOT the design tokens, which live in DESIGN.md):
|
|
34
|
+
// - designMdPath — optional path to the DESIGN.md (default <project-dir>/DESIGN.md).
|
|
35
|
+
// - shell — { heroHeadline, heroSub, footerTagline, navBrandMark }.
|
|
36
|
+
// - brand — { name, description }.
|
|
37
|
+
// - navLinks — [ { href, label } ]; labels used VERBATIM.
|
|
38
|
+
// - loadedPacks — string[] of loaded vertical packs.
|
|
39
|
+
// - packsWithComponents — string[]; one components-<pack>.css import per entry,
|
|
40
|
+
// in order (COMPOSE § Layout.astro).
|
|
41
|
+
// - disabledPacks — string[]; dormant packs still get their home/nav markers,
|
|
42
|
+
// never a visible entry point.
|
|
43
|
+
//
|
|
44
|
+
// What it writes (the 6 design-system files), by substituting {{…}} slots into
|
|
45
|
+
// the pinned skeletons — the fixed bulk is copied byte-for-byte:
|
|
46
|
+
// 1. src/styles/global.css — {{theme}} ← @theme palette
|
|
47
|
+
// 2. astro.config.mjs — MERGE (anchored codemod, not clobber)
|
|
48
|
+
// 3. src/layouts/Layout.astro — imports, fonts href, brand title
|
|
49
|
+
// 4. src/components/Navigation.astro — brand mark, nav links
|
|
50
|
+
// 5. src/components/Footer.astro — brand, tagline, nav links
|
|
51
|
+
// 6. src/pages/index.astro — hero copy, brand, home markers
|
|
52
|
+
//
|
|
53
|
+
// Token contract: guarantees the required-token set (STYLING.md § "Required
|
|
54
|
+
// tokens — the component-CSS template contract") resolves in @theme. Missing
|
|
55
|
+
// roles are DERIVED as a fail-safe (the Designer is expected to emit a complete
|
|
56
|
+
// set + a googleFontsHref; derivation is a safety net, not the hot path).
|
|
57
|
+
//
|
|
58
|
+
// Output: a single manifest JSON object on stdout (the orchestrator parses it
|
|
59
|
+
// the same way it parsed the subagent's return — same { status, phase, data,
|
|
60
|
+
// files } shape). Diagnostics go to stderr. Exit 0 on complete, 1 on a hard
|
|
61
|
+
// failure (missing scaffold / unrecoverable token gap / missing config anchor).
|
|
62
|
+
//
|
|
63
|
+
// astro only. Custom (non-astro) frontends never reach the Composer — the
|
|
64
|
+
// orchestrator only invokes this when frontend === "astro".
|
|
65
|
+
|
|
66
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
|
|
67
|
+
import { join, dirname, resolve, isAbsolute } from "node:path";
|
|
68
|
+
import { fileURLToPath } from "node:url";
|
|
69
|
+
import { execSync } from "node:child_process";
|
|
70
|
+
|
|
71
|
+
const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
|
|
72
|
+
const TEMPLATES = resolve(SCRIPT_DIR, "..", "references", "astro", "templates");
|
|
73
|
+
|
|
74
|
+
const projectDir = process.argv[2] ?? process.cwd();
|
|
75
|
+
|
|
76
|
+
function die(code, message) {
|
|
77
|
+
// Emit a structured failure manifest on stdout so the orchestrator parses a
|
|
78
|
+
// return either way, plus a human line on stderr. Hard-fail with exit 1.
|
|
79
|
+
console.error(`compose: ${message}`);
|
|
80
|
+
console.log(JSON.stringify({ status: "failed", phase: "compose", data: {}, files: [], errors: [{ code, message }] }, null, 2));
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ── read + validate input ─────────────────────────────────────────────────────
|
|
85
|
+
if (process.stdin.isTTY) die("NO_INPUT", "expected the compose input JSON on stdin");
|
|
86
|
+
let input;
|
|
87
|
+
try {
|
|
88
|
+
const raw = readFileSync(0, "utf8").trim();
|
|
89
|
+
if (!raw) die("EMPTY_INPUT", "stdin was empty — pass the compose input JSON object");
|
|
90
|
+
input = JSON.parse(raw);
|
|
91
|
+
} catch (e) {
|
|
92
|
+
die("BAD_JSON", `stdin is not valid JSON (${e.message})`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ── token source: DESIGN.md frontmatter (portable) ───────────────────────────
|
|
96
|
+
// Minimal frontmatter parser for the restricted DESIGN.md shape (groups of
|
|
97
|
+
// `key: scalar`, plus typography tokens with an indented `fontFamily:`). Not a
|
|
98
|
+
// general YAML parser — the body is never read. Color values are quoted in the
|
|
99
|
+
// emitted DESIGN.md because an unquoted `#hex` after `: ` is a YAML comment.
|
|
100
|
+
function unquote(v) {
|
|
101
|
+
v = v.trim();
|
|
102
|
+
const qc = v[0];
|
|
103
|
+
if (qc === '"' || qc === "'") {
|
|
104
|
+
// Return the quoted content up to the closing quote (handles a trailing
|
|
105
|
+
// ` # comment` after the closing quote, and \" / \\ escapes in "…").
|
|
106
|
+
let out = "";
|
|
107
|
+
for (let i = 1; i < v.length; i++) {
|
|
108
|
+
const c = v[i];
|
|
109
|
+
if (qc === '"' && c === "\\" && i + 1 < v.length) { out += v[++i]; continue; }
|
|
110
|
+
if (c === qc) break;
|
|
111
|
+
out += c;
|
|
112
|
+
}
|
|
113
|
+
return out;
|
|
114
|
+
}
|
|
115
|
+
const hash = v.search(/\s#/); // strip trailing inline comment on bare scalars
|
|
116
|
+
if (hash !== -1) v = v.slice(0, hash).trim();
|
|
117
|
+
return v;
|
|
118
|
+
}
|
|
119
|
+
// Parse a flow-style inline object — `{ fontFamily: "X", fontWeight: 600 }` —
|
|
120
|
+
// splitting top-level commas while respecting quotes (so `"Helvetica, Arial"`
|
|
121
|
+
// stays one value). Used for typography tokens authored in flow style.
|
|
122
|
+
function parseInlineObject(s) {
|
|
123
|
+
const inner = s.trim().replace(/^\{/, "").replace(/\}$/, "");
|
|
124
|
+
const obj = {};
|
|
125
|
+
let buf = "", inq = null;
|
|
126
|
+
const parts = [];
|
|
127
|
+
for (const ch of inner) {
|
|
128
|
+
if (inq) { if (ch === inq) inq = null; buf += ch; continue; }
|
|
129
|
+
if (ch === '"' || ch === "'") { inq = ch; buf += ch; continue; }
|
|
130
|
+
if (ch === ",") { parts.push(buf); buf = ""; continue; }
|
|
131
|
+
buf += ch;
|
|
132
|
+
}
|
|
133
|
+
if (buf.trim()) parts.push(buf);
|
|
134
|
+
for (const p of parts) {
|
|
135
|
+
const ci = p.indexOf(":");
|
|
136
|
+
if (ci === -1) continue;
|
|
137
|
+
obj[p.slice(0, ci).trim()] = unquote(p.slice(ci + 1));
|
|
138
|
+
}
|
|
139
|
+
return obj;
|
|
140
|
+
}
|
|
141
|
+
function parseFrontmatter(text) {
|
|
142
|
+
const m = text.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
143
|
+
if (!m) return null;
|
|
144
|
+
const root = {};
|
|
145
|
+
let group = null, sub = null;
|
|
146
|
+
for (const raw of m[1].split(/\r?\n/)) {
|
|
147
|
+
if (!raw.trim() || /^\s*#/.test(raw)) continue;
|
|
148
|
+
const indent = raw.match(/^ */)[0].length;
|
|
149
|
+
const ci = raw.indexOf(":");
|
|
150
|
+
if (ci === -1) continue;
|
|
151
|
+
const key = raw.slice(0, ci).trim();
|
|
152
|
+
const rest = raw.slice(ci + 1).trim();
|
|
153
|
+
if (indent === 0) {
|
|
154
|
+
if (rest === "") { root[key] = {}; group = key; sub = null; }
|
|
155
|
+
else if (rest.startsWith("{")) { root[key] = parseInlineObject(rest); group = null; sub = null; }
|
|
156
|
+
else { root[key] = unquote(rest); group = null; sub = null; }
|
|
157
|
+
} else if (indent === 2 && group) {
|
|
158
|
+
if (rest === "") { root[group][key] = {}; sub = key; }
|
|
159
|
+
else if (rest.startsWith("{")) { root[group][key] = parseInlineObject(rest); sub = null; }
|
|
160
|
+
else { root[group][key] = unquote(rest); sub = null; }
|
|
161
|
+
} else if (indent >= 4 && group && sub) {
|
|
162
|
+
if (typeof root[group][sub] !== "object") root[group][sub] = {};
|
|
163
|
+
root[group][sub][key] = unquote(rest);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return root;
|
|
167
|
+
}
|
|
168
|
+
// Standard DESIGN.md color roles → wix editorial `--color-*` names. wix-native
|
|
169
|
+
// keys (paper/ink/accent/…) are NOT in this table and pass through unchanged,
|
|
170
|
+
// winning on any conflict — so our own emission is lossless.
|
|
171
|
+
const COLOR_ROLE_TABLE = {
|
|
172
|
+
primary: "accent", secondary: "paper-warm", tertiary: "ink-soft", neutral: "mute",
|
|
173
|
+
surface: "paper", "on-surface": "ink", background: "paper", "on-background": "ink",
|
|
174
|
+
outline: "rule", error: "error",
|
|
175
|
+
};
|
|
176
|
+
function designMdToTokens(fm) {
|
|
177
|
+
// Single pass preserving source key order: each key maps via the role table
|
|
178
|
+
// (standard DESIGN.md role → wix name) or passes through unchanged (wix-native
|
|
179
|
+
// keys aren't in the table). Order-preserving so our own round-trip is
|
|
180
|
+
// byte-identical to the JSON path. (A file mixing a standard role and its wix
|
|
181
|
+
// synonym — e.g. both `surface` and `paper` — is pathological; last wins.)
|
|
182
|
+
const colors = {};
|
|
183
|
+
for (const [k, v] of Object.entries(fm.colors ?? {})) colors[COLOR_ROLE_TABLE[k] ?? k] = v;
|
|
184
|
+
const typ = fm.typography ?? {};
|
|
185
|
+
const famOf = (...keys) => { for (const key of keys) if (typ[key] && typ[key].fontFamily) return typ[key].fontFamily; return undefined; };
|
|
186
|
+
const fonts = {};
|
|
187
|
+
const disp = famOf("display", "h1", "heading", "title");
|
|
188
|
+
const body = famOf("body", "p", "text", "base", "paragraph");
|
|
189
|
+
if (disp) fonts.display = disp;
|
|
190
|
+
if (body) fonts.body = body;
|
|
191
|
+
if (typ.mono && typ.mono.fontFamily) fonts.mono = typ.mono.fontFamily;
|
|
192
|
+
return {
|
|
193
|
+
colors,
|
|
194
|
+
fonts,
|
|
195
|
+
spacing: fm.spacing ?? {},
|
|
196
|
+
radii: fm.rounded ?? {}, // DESIGN.md calls corner radii `rounded`
|
|
197
|
+
containers: fm.containers ?? {}, // custom group
|
|
198
|
+
...(typeof fm.googleFontsHref === "string" ? { googleFontsHref: fm.googleFontsHref } : {}),
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// DESIGN.md is the single token source — read it, parse frontmatter only.
|
|
203
|
+
const designMdRel = input.designMdPath ?? "DESIGN.md";
|
|
204
|
+
const designMdPath = isAbsolute(designMdRel) ? designMdRel : join(projectDir, designMdRel);
|
|
205
|
+
if (!existsSync(designMdPath)) die("DESIGN_MD_MISSING", `no DESIGN.md at ${designMdPath} — the Designer must author it first`);
|
|
206
|
+
const fm = parseFrontmatter(readFileSync(designMdPath, "utf8"));
|
|
207
|
+
if (!fm) die("DESIGN_MD_BAD", `could not parse YAML frontmatter from ${designMdPath}`);
|
|
208
|
+
const designTokens = designMdToTokens(fm);
|
|
209
|
+
const shell = input.shell ?? {};
|
|
210
|
+
const brand = input.brand ?? {};
|
|
211
|
+
const navLinks = Array.isArray(input.navLinks) ? input.navLinks : [];
|
|
212
|
+
const loadedPacks = Array.isArray(input.loadedPacks) ? input.loadedPacks : [];
|
|
213
|
+
const packsWithComponents = Array.isArray(input.packsWithComponents) ? input.packsWithComponents : [];
|
|
214
|
+
const disabledPacks = Array.isArray(input.disabledPacks) ? input.disabledPacks : [];
|
|
215
|
+
|
|
216
|
+
const brandName = brand.name ?? "Brand";
|
|
217
|
+
|
|
218
|
+
// ── small color helper (derivation fail-safe only) ────────────────────────────
|
|
219
|
+
function parseHex(hex) {
|
|
220
|
+
if (typeof hex !== "string") return null;
|
|
221
|
+
let h = hex.trim().replace(/^#/, "");
|
|
222
|
+
if (h.length === 3) h = h.split("").map((c) => c + c).join("");
|
|
223
|
+
if (!/^[0-9a-fA-F]{6}$/.test(h)) return null;
|
|
224
|
+
return [0, 2, 4].map((i) => parseInt(h.slice(i, i + 2), 16));
|
|
225
|
+
}
|
|
226
|
+
function toHex([r, g, b]) {
|
|
227
|
+
return "#" + [r, g, b].map((v) => Math.max(0, Math.min(255, Math.round(v))).toString(16).padStart(2, "0")).join("");
|
|
228
|
+
}
|
|
229
|
+
// mix `a` toward `b` by weight w (0..1). Falls back to `a` if either unparseable.
|
|
230
|
+
function mix(a, b, w) {
|
|
231
|
+
const ca = parseHex(a), cb = parseHex(b);
|
|
232
|
+
if (!ca || !cb) return a ?? b ?? "#000000";
|
|
233
|
+
return toHex(ca.map((v, i) => v + (cb[i] - v) * w));
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// ── build the @theme palette + guarantee the required-token contract ──────────
|
|
237
|
+
const SYSTEM_FONTS = new Set([
|
|
238
|
+
"system-ui", "-apple-system", "blinkmacsystemfont", "segoe ui", "sans-serif",
|
|
239
|
+
"serif", "monospace", "ui-sans-serif", "ui-serif", "ui-monospace", "inherit",
|
|
240
|
+
]);
|
|
241
|
+
|
|
242
|
+
const colors = { ...(designTokens.colors ?? {}) };
|
|
243
|
+
const fonts = { ...(designTokens.fonts ?? {}) };
|
|
244
|
+
const spacing = { ...(designTokens.spacing ?? {}) };
|
|
245
|
+
const radii = { ...(designTokens.radii ?? {}) };
|
|
246
|
+
const containers = { ...(designTokens.containers ?? {}) };
|
|
247
|
+
|
|
248
|
+
const derived = [];
|
|
249
|
+
function ensure(group, key, value, prefix) {
|
|
250
|
+
if (group[key] === undefined || group[key] === null || group[key] === "") {
|
|
251
|
+
group[key] = value;
|
|
252
|
+
derived.push(`${prefix}${key}`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Colors — required core first (sane neutral defaults if the Designer omitted a
|
|
257
|
+
// core role), then warm/soft derivations described in COMPOSE § Token contract.
|
|
258
|
+
ensure(colors, "paper", "#ffffff", "--color-");
|
|
259
|
+
ensure(colors, "ink", "#1a1a1a", "--color-");
|
|
260
|
+
ensure(colors, "accent", colors.ink, "--color-");
|
|
261
|
+
ensure(colors, "mute", mix(colors.ink, colors.paper, 0.45), "--color-");
|
|
262
|
+
ensure(colors, "rule", mix(colors.ink, colors.paper, 0.85), "--color-");
|
|
263
|
+
ensure(colors, "paper-warm", mix(colors.paper, colors.accent, 0.06), "--color-"); // paper warmed
|
|
264
|
+
ensure(colors, "ink-soft", mix(colors.ink, colors.paper, 0.25), "--color-"); // ink lightened
|
|
265
|
+
// NOTE: cream is derived but not in REQUIRED; gift-cards CSS expects
|
|
266
|
+
// --color-cream-deep (undeclared) and relies on the var() fallback — reconcile
|
|
267
|
+
// with STYLING.md token contract.
|
|
268
|
+
ensure(colors, "cream", mix(colors.paper, "#fbf3e0", 0.5), "--color-");
|
|
269
|
+
ensure(colors, "error", "#c0392b", "--color-");
|
|
270
|
+
|
|
271
|
+
// Fonts — display + body required.
|
|
272
|
+
ensure(fonts, "display", "Georgia, serif", "--font-");
|
|
273
|
+
ensure(fonts, "body", "system-ui, sans-serif", "--font-");
|
|
274
|
+
|
|
275
|
+
// Spacing — full 2xs..4xl scale.
|
|
276
|
+
const SPACING_DEFAULTS = {
|
|
277
|
+
"2xs": "0.25rem", xs: "0.5rem", sm: "0.75rem", md: "1rem", lg: "1.5rem",
|
|
278
|
+
xl: "2rem", "2xl": "3rem", "3xl": "4rem", "4xl": "6rem",
|
|
279
|
+
};
|
|
280
|
+
for (const [k, v] of Object.entries(SPACING_DEFAULTS)) ensure(spacing, k, v, "--spacing-");
|
|
281
|
+
|
|
282
|
+
// Radii — sm + md required.
|
|
283
|
+
ensure(radii, "sm", "0.25rem", "--radius-");
|
|
284
|
+
ensure(radii, "md", "0.5rem", "--radius-");
|
|
285
|
+
|
|
286
|
+
// Containers — content widths, a separate axis from spacing.
|
|
287
|
+
const CONTAINER_DEFAULTS = { prose: "42rem", md: "48rem", "3xl": "60rem", "6xl": "72rem" };
|
|
288
|
+
for (const [k, v] of Object.entries(CONTAINER_DEFAULTS)) ensure(containers, k, v, "--container-");
|
|
289
|
+
|
|
290
|
+
// Assemble the @theme block, grouped + ordered for readability.
|
|
291
|
+
const themeLines = [];
|
|
292
|
+
const emitGroup = (prefix, obj) => {
|
|
293
|
+
for (const [k, v] of Object.entries(obj)) themeLines.push(` ${prefix}${k}: ${v};`);
|
|
294
|
+
};
|
|
295
|
+
emitGroup("--color-", colors);
|
|
296
|
+
themeLines.push("");
|
|
297
|
+
emitGroup("--font-", fonts);
|
|
298
|
+
themeLines.push("");
|
|
299
|
+
emitGroup("--spacing-", spacing);
|
|
300
|
+
themeLines.push("");
|
|
301
|
+
emitGroup("--radius-", radii);
|
|
302
|
+
themeLines.push("");
|
|
303
|
+
emitGroup("--container-", containers);
|
|
304
|
+
const themeBlock = themeLines.join("\n");
|
|
305
|
+
|
|
306
|
+
// ── self-checks (assertions) ──────────────────────────────────────────────────
|
|
307
|
+
const errors = [];
|
|
308
|
+
|
|
309
|
+
// Required-token coverage (after derivation, every required role must resolve).
|
|
310
|
+
const REQUIRED = [
|
|
311
|
+
...["paper", "paper-warm", "ink", "mute", "rule", "accent"].map((k) => `--color-${k}`),
|
|
312
|
+
"--font-display", "--font-body",
|
|
313
|
+
...["2xs", "xs", "sm", "md", "lg", "xl", "2xl", "3xl", "4xl"].map((k) => `--spacing-${k}`),
|
|
314
|
+
"--radius-sm", "--radius-md",
|
|
315
|
+
...["prose", "md", "3xl", "6xl"].map((k) => `--container-${k}`),
|
|
316
|
+
];
|
|
317
|
+
const present = new Set([
|
|
318
|
+
...Object.keys(colors).map((k) => `--color-${k}`),
|
|
319
|
+
...Object.keys(fonts).map((k) => `--font-${k}`),
|
|
320
|
+
...Object.keys(spacing).map((k) => `--spacing-${k}`),
|
|
321
|
+
...Object.keys(radii).map((k) => `--radius-${k}`),
|
|
322
|
+
...Object.keys(containers).map((k) => `--container-${k}`),
|
|
323
|
+
]);
|
|
324
|
+
for (const tok of REQUIRED) {
|
|
325
|
+
if (!present.has(tok)) errors.push({ code: "MISSING_REQUIRED_TOKEN", token: tok });
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Container ≠ spacing: a --container-* value must never equal a --spacing-* value.
|
|
329
|
+
const spacingValues = new Set(Object.values(spacing));
|
|
330
|
+
for (const [k, v] of Object.entries(containers)) {
|
|
331
|
+
if (spacingValues.has(v)) errors.push({ code: "CONTAINER_EQUALS_SPACING", token: `--container-${k}`, value: v });
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// ── skeleton load + substitution helpers ──────────────────────────────────────
|
|
335
|
+
function readTemplate(name) {
|
|
336
|
+
const p = join(TEMPLATES, name);
|
|
337
|
+
if (!existsSync(p)) die("TEMPLATE_MISSING", `skeleton not found at ${p}`);
|
|
338
|
+
return readFileSync(p, "utf8");
|
|
339
|
+
}
|
|
340
|
+
function writeProject(rel, content) {
|
|
341
|
+
const dest = join(projectDir, rel);
|
|
342
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
343
|
+
writeFileSync(dest, content);
|
|
344
|
+
}
|
|
345
|
+
function escAttr(s) {
|
|
346
|
+
return String(s ?? "").replace(/"/g, """);
|
|
347
|
+
}
|
|
348
|
+
// Strip the leading "// ── Composer skeleton: … ──" authoring-comment block from
|
|
349
|
+
// an .astro skeleton's frontmatter. Those lines document the {{…}} slots (and
|
|
350
|
+
// thus contain placeholder text that must NOT be substituted) and are not part
|
|
351
|
+
// of the shipped site — the real Composer output drops them too. Removes the
|
|
352
|
+
// contiguous run of `//` comment lines immediately after the opening `---`.
|
|
353
|
+
function stripAstroHeader(src) {
|
|
354
|
+
return src.replace(/^(---\n)((?:[ \t]*\/\/.*\n)+)/, "$1");
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// ── Google Fonts href ─────────────────────────────────────────────────────────
|
|
358
|
+
// Prefer the Designer-supplied href; otherwise build one deterministically with
|
|
359
|
+
// a standard weight set. Returns ""
|
|
360
|
+
// when both families are system fonts (caller drops the <link>).
|
|
361
|
+
function buildGoogleHref() {
|
|
362
|
+
if (typeof designTokens.googleFontsHref === "string" && designTokens.googleFontsHref.trim()) {
|
|
363
|
+
return designTokens.googleFontsHref.trim();
|
|
364
|
+
}
|
|
365
|
+
const families = [];
|
|
366
|
+
for (const fam of [fonts.display, fonts.body]) {
|
|
367
|
+
if (typeof fam !== "string") continue;
|
|
368
|
+
const first = fam.split(",")[0].trim();
|
|
369
|
+
if (!first || SYSTEM_FONTS.has(first.toLowerCase())) continue;
|
|
370
|
+
if (!families.includes(first)) families.push(first);
|
|
371
|
+
}
|
|
372
|
+
if (families.length === 0) return "";
|
|
373
|
+
const q = families
|
|
374
|
+
.map((f) => `family=${f.replace(/\s+/g, "+")}:wght@400;500;600;700`)
|
|
375
|
+
.join("&");
|
|
376
|
+
return `https://fonts.googleapis.com/css2?${q}&display=swap`;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// ── 1. global.css ──────────────────────────────────────────────────────────────
|
|
380
|
+
{
|
|
381
|
+
const skeleton = readTemplate("global.css");
|
|
382
|
+
// Replace the first `@theme { … }` block (its comment has no braces, so the
|
|
383
|
+
// first `}` is its close). Everything else is literal fixed bulk.
|
|
384
|
+
const out = skeleton.replace(/@theme\s*\{[\s\S]*?\n\}/, `@theme {\n${themeBlock}\n}`);
|
|
385
|
+
if (out === skeleton) die("ANCHOR_THEME", "could not locate the @theme block in global.css skeleton");
|
|
386
|
+
writeProject("src/styles/global.css", out);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// ── 2. astro.config.mjs (anchored MERGE, fail loud on missing anchor) ─────────
|
|
390
|
+
{
|
|
391
|
+
const dest = join(projectDir, "astro.config.mjs");
|
|
392
|
+
// The scaffold may briefly be in flight; the new wiring invokes compose only
|
|
393
|
+
// after Setup Step 1 awaited the scaffold, so a short retry is belt-and-braces.
|
|
394
|
+
let src = null;
|
|
395
|
+
for (let attempt = 0; attempt < 10; attempt++) {
|
|
396
|
+
if (existsSync(dest)) { src = readFileSync(dest, "utf8"); break; }
|
|
397
|
+
try { execSync("sleep 1"); } catch { /* ignore */ }
|
|
398
|
+
}
|
|
399
|
+
if (src === null) die("SCAFFOLD_NOT_COMPLETE", `astro.config.mjs not found at ${dest} after retries`);
|
|
400
|
+
|
|
401
|
+
const hasImport = /import\s+tailwindcss\s+from\s+["']@tailwindcss\/vite["']/.test(src);
|
|
402
|
+
const hasCall = /tailwindcss\s*\(\s*\)/.test(src);
|
|
403
|
+
|
|
404
|
+
// (1a) import — insert after the last top-level import statement.
|
|
405
|
+
if (!hasImport) {
|
|
406
|
+
const importRe = /^import\b.*$/gm;
|
|
407
|
+
let last = null, m;
|
|
408
|
+
while ((m = importRe.exec(src)) !== null) last = m;
|
|
409
|
+
if (!last) die("ANCHOR_IMPORT", "no import statement found in astro.config.mjs to anchor the @tailwindcss/vite import");
|
|
410
|
+
const insertAt = last.index + last[0].length;
|
|
411
|
+
src = src.slice(0, insertAt) + `\nimport tailwindcss from "@tailwindcss/vite";` + src.slice(insertAt);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// (1b) merge tailwindcss() into vite.plugins (idempotent on hasCall).
|
|
415
|
+
if (!hasCall) {
|
|
416
|
+
if (/plugins\s*:\s*\[/.test(src)) {
|
|
417
|
+
src = src.replace(/plugins\s*:\s*\[/, (mm) => `${mm}tailwindcss(), `);
|
|
418
|
+
} else if (/\bvite\s*:\s*\{/.test(src)) {
|
|
419
|
+
src = src.replace(/(\bvite\s*:\s*\{)/, `$1\n plugins: [tailwindcss()],`);
|
|
420
|
+
} else if (/defineConfig\s*\(\s*\{/.test(src)) {
|
|
421
|
+
src = src.replace(/(defineConfig\s*\(\s*\{)/, `$1\n vite: { plugins: [tailwindcss()] },`);
|
|
422
|
+
} else {
|
|
423
|
+
die("ANCHOR_VITE", "could not find vite.plugins, a vite block, or defineConfig({ to register @tailwindcss/vite");
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// (2) process.env → globalThis guard (TS-safe under strict tsc --noEmit).
|
|
428
|
+
const GUARD = `const isBuild =\n (/** @type {any} */ (globalThis)).process?.env?.NODE_ENV === "production";`;
|
|
429
|
+
const alreadyGuarded = /\(globalThis\)\)\.process\?\.env\?\.NODE_ENV/.test(src);
|
|
430
|
+
const bareRe = /const\s+isBuild\s*=\s*process\.env\.NODE_ENV\s*===?\s*["']production["']\s*;/;
|
|
431
|
+
if (!alreadyGuarded) {
|
|
432
|
+
if (bareRe.test(src)) {
|
|
433
|
+
src = src.replace(bareRe, GUARD);
|
|
434
|
+
} else if (/process\.env/.test(src)) {
|
|
435
|
+
die("ANCHOR_PROCESS_ENV", "found a bare process.env reference but not the expected `const isBuild = process.env.NODE_ENV …` line to guard");
|
|
436
|
+
}
|
|
437
|
+
// else: no process.env at all — nothing to guard, leave as-is.
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
writeProject("astro.config.mjs", src);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// ── 3. Layout.astro ────────────────────────────────────────────────────────────
|
|
444
|
+
const componentCssImports = packsWithComponents.map(
|
|
445
|
+
(pack) => `import '../styles/components-${pack}.css';`,
|
|
446
|
+
);
|
|
447
|
+
{
|
|
448
|
+
let out = stripAstroHeader(readTemplate("Layout.astro"));
|
|
449
|
+
// {{components-css-imports}} — one import per pack, or drop the whole line.
|
|
450
|
+
if (componentCssImports.length) {
|
|
451
|
+
out = out.replace(/^\{\{components-css-imports\}\}\s*$/m, componentCssImports.join("\n"));
|
|
452
|
+
} else {
|
|
453
|
+
out = out.replace(/^\{\{components-css-imports\}\}\s*\n/m, "");
|
|
454
|
+
}
|
|
455
|
+
// {{fonts.googleHref}} — drop the <link> when no web fonts.
|
|
456
|
+
const href = buildGoogleHref();
|
|
457
|
+
if (href) {
|
|
458
|
+
out = out.replace(/\{\{fonts\.googleHref\}\}/g, escAttr(href));
|
|
459
|
+
} else {
|
|
460
|
+
out = out.replace(/^.*\{\{fonts\.googleHref\}\}.*\n/m, "");
|
|
461
|
+
}
|
|
462
|
+
out = out.replaceAll("{{brand.name}}", brandName);
|
|
463
|
+
writeProject("src/layouts/Layout.astro", out);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// ── nav-link rendering ──────────────────────────────────────────────────────────
|
|
467
|
+
const navItems = navLinks
|
|
468
|
+
.filter((l) => l && l.href != null && l.label != null)
|
|
469
|
+
.map((l) => ({ href: String(l.href), label: String(l.label) }));
|
|
470
|
+
|
|
471
|
+
// ── 4. Navigation.astro ──────────────────────────────────────────────────────────
|
|
472
|
+
{
|
|
473
|
+
let out = stripAstroHeader(readTemplate("Navigation.astro"));
|
|
474
|
+
out = out.replaceAll("{{shell.navBrandMark}}", shell.navBrandMark ?? brandName);
|
|
475
|
+
const links = navItems
|
|
476
|
+
.map((l) => ` <li class="site-nav-item"><a href="${escAttr(l.href)}">${l.label}</a></li>`)
|
|
477
|
+
.join("\n");
|
|
478
|
+
out = out.replace(/^\s*\{\{nav\.links\}\}\s*$/m, links);
|
|
479
|
+
writeProject("src/components/Navigation.astro", out);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// ── 5. Footer.astro ──────────────────────────────────────────────────────────────
|
|
483
|
+
{
|
|
484
|
+
let out = stripAstroHeader(readTemplate("Footer.astro"));
|
|
485
|
+
out = out.replaceAll("{{brand.name}}", brandName);
|
|
486
|
+
out = out.replaceAll("{{shell.footerTagline}}", shell.footerTagline ?? "");
|
|
487
|
+
const links = navItems
|
|
488
|
+
.map((l) => ` <li><a href="${escAttr(l.href)}">${l.label}</a></li>`)
|
|
489
|
+
.join("\n");
|
|
490
|
+
out = out.replace(/^\s*\{\{nav\.links\}\}\s*$/m, links);
|
|
491
|
+
writeProject("src/components/Footer.astro", out);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// ── 6. index.astro ────────────────────────────────────────────────────────────────
|
|
495
|
+
// Home markers: one `<!-- home:<pack> -->` per contributing pack. Today
|
|
496
|
+
// stores + bookings + gift-cards contribute a home section. Disabled packs
|
|
497
|
+
// (gift-cards) still get their marker (markers are their only acceptable touchpoint).
|
|
498
|
+
const HOME_CONTRIBUTING = ["stores", "bookings", "gift-cards"]; // canonical order
|
|
499
|
+
const homePool = new Set([...loadedPacks, ...disabledPacks]);
|
|
500
|
+
const homeMarkerPacks = HOME_CONTRIBUTING.filter((p) => homePool.has(p));
|
|
501
|
+
{
|
|
502
|
+
let out = stripAstroHeader(readTemplate("index.astro"));
|
|
503
|
+
out = out.replaceAll("{{shell.heroHeadline}}", shell.heroHeadline ?? brandName);
|
|
504
|
+
out = out.replaceAll("{{shell.heroSub}}", shell.heroSub ?? "");
|
|
505
|
+
out = out.replaceAll("{{brand.name}}", brandName);
|
|
506
|
+
const markers = homeMarkerPacks.map((p) => ` <!-- home:${p} -->`).join("\n");
|
|
507
|
+
out = out.replace(/^\s*\{\{home-markers\}\}\s*$/m, markers);
|
|
508
|
+
writeProject("src/pages/index.astro", out);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// ── manifest (the orchestrator parses this off stdout) ────────────────────────
|
|
512
|
+
const filesWritten = [
|
|
513
|
+
"src/styles/global.css",
|
|
514
|
+
"astro.config.mjs",
|
|
515
|
+
"src/layouts/Layout.astro",
|
|
516
|
+
"src/components/Navigation.astro",
|
|
517
|
+
"src/components/Footer.astro",
|
|
518
|
+
"src/pages/index.astro",
|
|
519
|
+
];
|
|
520
|
+
const manifest = {
|
|
521
|
+
status: errors.length ? "partial" : "complete",
|
|
522
|
+
phase: "compose",
|
|
523
|
+
data: {
|
|
524
|
+
filesWritten,
|
|
525
|
+
componentCssImports: packsWithComponents,
|
|
526
|
+
homeMarkers: homeMarkerPacks.map((p) => `home:${p}`),
|
|
527
|
+
tokensApplied: {
|
|
528
|
+
colors: Object.keys(colors).length,
|
|
529
|
+
spacing: Object.keys(spacing).length,
|
|
530
|
+
containers: Object.keys(containers).length,
|
|
531
|
+
radii: Object.keys(radii).length,
|
|
532
|
+
fonts: Object.keys(fonts).length,
|
|
533
|
+
},
|
|
534
|
+
...(derived.length ? { derivedTokens: derived } : {}),
|
|
535
|
+
},
|
|
536
|
+
files: filesWritten,
|
|
537
|
+
...(errors.length ? { errors } : {}),
|
|
538
|
+
};
|
|
539
|
+
console.log(JSON.stringify(manifest, null, 2));
|
|
540
|
+
process.exit(0);
|