@doswiftly/cli 0.1.1
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 +357 -0
- package/bin/doswiftly.js +2 -0
- package/dist/commands/auth-github.d.ts +6 -0
- package/dist/commands/auth-github.d.ts.map +1 -0
- package/dist/commands/auth-github.js +89 -0
- package/dist/commands/auth-github.js.map +1 -0
- package/dist/commands/auth-token.d.ts +12 -0
- package/dist/commands/auth-token.d.ts.map +1 -0
- package/dist/commands/auth-token.js +43 -0
- package/dist/commands/auth-token.js.map +1 -0
- package/dist/commands/auth.d.ts +22 -0
- package/dist/commands/auth.d.ts.map +1 -0
- package/dist/commands/auth.js +348 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/check.d.ts +5 -0
- package/dist/commands/check.d.ts.map +1 -0
- package/dist/commands/check.js +234 -0
- package/dist/commands/check.js.map +1 -0
- package/dist/commands/config.d.ts +3 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +104 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/deploy.d.ts +37 -0
- package/dist/commands/deploy.d.ts.map +1 -0
- package/dist/commands/deploy.js +580 -0
- package/dist/commands/deploy.js.map +1 -0
- package/dist/commands/dev.d.ts +8 -0
- package/dist/commands/dev.d.ts.map +1 -0
- package/dist/commands/dev.js +83 -0
- package/dist/commands/dev.js.map +1 -0
- package/dist/commands/doctor.d.ts +5 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +363 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/domain.d.ts +13 -0
- package/dist/commands/domain.d.ts.map +1 -0
- package/dist/commands/domain.js +128 -0
- package/dist/commands/domain.js.map +1 -0
- package/dist/commands/env.d.ts +25 -0
- package/dist/commands/env.d.ts.map +1 -0
- package/dist/commands/env.js +228 -0
- package/dist/commands/env.js.map +1 -0
- package/dist/commands/init.d.ts +11 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +1028 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/inspect.d.ts +12 -0
- package/dist/commands/inspect.d.ts.map +1 -0
- package/dist/commands/inspect.js +162 -0
- package/dist/commands/inspect.js.map +1 -0
- package/dist/commands/migrate.d.ts +18 -0
- package/dist/commands/migrate.d.ts.map +1 -0
- package/dist/commands/migrate.js +355 -0
- package/dist/commands/migrate.js.map +1 -0
- package/dist/commands/preview.d.ts +29 -0
- package/dist/commands/preview.d.ts.map +1 -0
- package/dist/commands/preview.js +199 -0
- package/dist/commands/preview.js.map +1 -0
- package/dist/commands/proxy.d.ts +9 -0
- package/dist/commands/proxy.d.ts.map +1 -0
- package/dist/commands/proxy.js +37 -0
- package/dist/commands/proxy.js.map +1 -0
- package/dist/commands/sdk.d.ts +5 -0
- package/dist/commands/sdk.d.ts.map +1 -0
- package/dist/commands/sdk.js +82 -0
- package/dist/commands/sdk.js.map +1 -0
- package/dist/commands/template.d.ts +107 -0
- package/dist/commands/template.d.ts.map +1 -0
- package/dist/commands/template.js +1309 -0
- package/dist/commands/template.js.map +1 -0
- package/dist/commands/types.d.ts +5 -0
- package/dist/commands/types.d.ts.map +1 -0
- package/dist/commands/types.js +82 -0
- package/dist/commands/types.js.map +1 -0
- package/dist/commands/update.d.ts +2 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +103 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/commands/upgrade.d.ts +18 -0
- package/dist/commands/upgrade.d.ts.map +1 -0
- package/dist/commands/upgrade.js +55 -0
- package/dist/commands/upgrade.js.map +1 -0
- package/dist/commands/verify.d.ts +5 -0
- package/dist/commands/verify.d.ts.map +1 -0
- package/dist/commands/verify.js +232 -0
- package/dist/commands/verify.js.map +1 -0
- package/dist/commands/whoami.d.ts +5 -0
- package/dist/commands/whoami.d.ts.map +1 -0
- package/dist/commands/whoami.js +60 -0
- package/dist/commands/whoami.js.map +1 -0
- package/dist/config/types.d.ts +173 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +48 -0
- package/dist/config/types.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +416 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/api-url.d.ts +14 -0
- package/dist/lib/api-url.d.ts.map +1 -0
- package/dist/lib/api-url.js +24 -0
- package/dist/lib/api-url.js.map +1 -0
- package/dist/lib/api.d.ts +67 -0
- package/dist/lib/api.d.ts.map +1 -0
- package/dist/lib/api.js +36 -0
- package/dist/lib/api.js.map +1 -0
- package/dist/lib/config.d.ts +39 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +195 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/env-storage.d.ts +140 -0
- package/dist/lib/env-storage.d.ts.map +1 -0
- package/dist/lib/env-storage.js +464 -0
- package/dist/lib/env-storage.js.map +1 -0
- package/dist/lib/errors.d.ts +61 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +204 -0
- package/dist/lib/errors.js.map +1 -0
- package/dist/lib/i18n.d.ts +99 -0
- package/dist/lib/i18n.d.ts.map +1 -0
- package/dist/lib/i18n.js +184 -0
- package/dist/lib/i18n.js.map +1 -0
- package/dist/lib/logger.d.ts +95 -0
- package/dist/lib/logger.d.ts.map +1 -0
- package/dist/lib/logger.js +168 -0
- package/dist/lib/logger.js.map +1 -0
- package/dist/lib/package-manager.d.ts +91 -0
- package/dist/lib/package-manager.d.ts.map +1 -0
- package/dist/lib/package-manager.js +205 -0
- package/dist/lib/package-manager.js.map +1 -0
- package/dist/lib/proxy-server.d.ts +24 -0
- package/dist/lib/proxy-server.d.ts.map +1 -0
- package/dist/lib/proxy-server.js +173 -0
- package/dist/lib/proxy-server.js.map +1 -0
- package/dist/lib/select-with-back.d.ts +34 -0
- package/dist/lib/select-with-back.d.ts.map +1 -0
- package/dist/lib/select-with-back.js +94 -0
- package/dist/lib/select-with-back.js.map +1 -0
- package/dist/lib/shared-api-client.d.ts +40 -0
- package/dist/lib/shared-api-client.d.ts.map +1 -0
- package/dist/lib/shared-api-client.js +92 -0
- package/dist/lib/shared-api-client.js.map +1 -0
- package/dist/lib/wizard-engine.d.ts +128 -0
- package/dist/lib/wizard-engine.d.ts.map +1 -0
- package/dist/lib/wizard-engine.js +168 -0
- package/dist/lib/wizard-engine.js.map +1 -0
- package/package.json +85 -0
- package/templates/storefront-minimal/.env.example +10 -0
- package/templates/storefront-minimal/.github/workflows/build-template.yml +109 -0
- package/templates/storefront-minimal/app/globals.css +18 -0
- package/templates/storefront-minimal/app/layout.tsx +26 -0
- package/templates/storefront-minimal/app/page.tsx +93 -0
- package/templates/storefront-minimal/lib/graphql-client.ts +23 -0
- package/templates/storefront-minimal/next.config.ts +15 -0
- package/templates/storefront-minimal/open-next.config.ts +3 -0
- package/templates/storefront-minimal/package.json +30 -0
- package/templates/storefront-minimal/postcss.config.mjs +5 -0
- package/templates/storefront-minimal/tailwind.config.ts +14 -0
- package/templates/storefront-minimal/tsconfig.json +27 -0
- package/templates/storefront-minimal/wrangler.toml +9 -0
- package/templates/storefront-nextjs/.env.example +68 -0
- package/templates/storefront-nextjs/.github/workflows/build-template.yml +109 -0
- package/templates/storefront-nextjs/.github/workflows/deploy.yml +25 -0
- package/templates/storefront-nextjs/.github/workflows/preview.yml +22 -0
- package/templates/storefront-nextjs/README.md +520 -0
- package/templates/storefront-nextjs/app/account/orders/page.tsx +216 -0
- package/templates/storefront-nextjs/app/account/page.tsx +167 -0
- package/templates/storefront-nextjs/app/auth/login/page.tsx +135 -0
- package/templates/storefront-nextjs/app/auth/register/page.tsx +228 -0
- package/templates/storefront-nextjs/app/cart/page.tsx +263 -0
- package/templates/storefront-nextjs/app/categories/[slug]/page.tsx +200 -0
- package/templates/storefront-nextjs/app/categories/page.tsx +58 -0
- package/templates/storefront-nextjs/app/checkout/page.tsx +351 -0
- package/templates/storefront-nextjs/app/collections/[slug]/page.tsx +158 -0
- package/templates/storefront-nextjs/app/collections/page.tsx +61 -0
- package/templates/storefront-nextjs/app/globals.css +98 -0
- package/templates/storefront-nextjs/app/layout.tsx +39 -0
- package/templates/storefront-nextjs/app/page.tsx +136 -0
- package/templates/storefront-nextjs/app/products/[slug]/page.tsx +119 -0
- package/templates/storefront-nextjs/app/products/page.tsx +107 -0
- package/templates/storefront-nextjs/app/search/page.tsx +127 -0
- package/templates/storefront-nextjs/components/auth/auth-guard.tsx +94 -0
- package/templates/storefront-nextjs/components/commerce/add-to-cart-button.tsx +77 -0
- package/templates/storefront-nextjs/components/commerce/cart-icon.tsx +29 -0
- package/templates/storefront-nextjs/components/commerce/currency-selector.tsx +217 -0
- package/templates/storefront-nextjs/components/commerce/pagination.tsx +62 -0
- package/templates/storefront-nextjs/components/commerce/product-actions.tsx +135 -0
- package/templates/storefront-nextjs/components/commerce/product-filters.tsx +109 -0
- package/templates/storefront-nextjs/components/commerce/product-price.tsx +375 -0
- package/templates/storefront-nextjs/components/commerce/search-input.tsx +178 -0
- package/templates/storefront-nextjs/components/commerce/sort-select.tsx +64 -0
- package/templates/storefront-nextjs/components/commerce/variant-selector.tsx +210 -0
- package/templates/storefront-nextjs/components/layout/footer.tsx +107 -0
- package/templates/storefront-nextjs/components/layout/header.tsx +104 -0
- package/templates/storefront-nextjs/components/providers.tsx +62 -0
- package/templates/storefront-nextjs/lib/auth/routes.ts +52 -0
- package/templates/storefront-nextjs/lib/currency.tsx +140 -0
- package/templates/storefront-nextjs/lib/format.ts +159 -0
- package/templates/storefront-nextjs/lib/graphql-queries.ts +629 -0
- package/templates/storefront-nextjs/lib/hooks.ts +30 -0
- package/templates/storefront-nextjs/middleware.ts +80 -0
- package/templates/storefront-nextjs/next.config.ts +37 -0
- package/templates/storefront-nextjs/open-next.config.ts +3 -0
- package/templates/storefront-nextjs/package.dev.json +30 -0
- package/templates/storefront-nextjs/package.json +32 -0
- package/templates/storefront-nextjs/package.json.template +32 -0
- package/templates/storefront-nextjs/postcss.config.mjs +8 -0
- package/templates/storefront-nextjs/tailwind.config.ts +111 -0
- package/templates/storefront-nextjs/tsconfig.json +27 -0
- package/templates/storefront-nextjs/wrangler.toml +9 -0
- package/templates/storefront-nextjs-shadcn/.env.example +68 -0
- package/templates/storefront-nextjs-shadcn/.github/workflows/build-template.yml +109 -0
- package/templates/storefront-nextjs-shadcn/.github/workflows/deploy.yml +25 -0
- package/templates/storefront-nextjs-shadcn/.github/workflows/preview.yml +22 -0
- package/templates/storefront-nextjs-shadcn/CART_INTEGRATION.md +282 -0
- package/templates/storefront-nextjs-shadcn/CLAUDE.md +96 -0
- package/templates/storefront-nextjs-shadcn/GRAPHQL_DOCUMENT_NAMES.md +190 -0
- package/templates/storefront-nextjs-shadcn/GRAPHQL_ERROR_HANDLING.md +263 -0
- package/templates/storefront-nextjs-shadcn/GRAPHQL_FIXES_SUMMARY.md +135 -0
- package/templates/storefront-nextjs-shadcn/GRAPHQL_INTEGRATION_COMPLETE.md +142 -0
- package/templates/storefront-nextjs-shadcn/INTEGRATION_CHECKLIST.md +448 -0
- package/templates/storefront-nextjs-shadcn/PRODUCT_DETAIL_PAGE_IMPLEMENTATION.md +307 -0
- package/templates/storefront-nextjs-shadcn/README.md +195 -0
- package/templates/storefront-nextjs-shadcn/THEME_CUSTOMIZATION.md +245 -0
- package/templates/storefront-nextjs-shadcn/app/about/page.tsx +34 -0
- package/templates/storefront-nextjs-shadcn/app/account/addresses/page.tsx +215 -0
- package/templates/storefront-nextjs-shadcn/app/account/loyalty/page.tsx +484 -0
- package/templates/storefront-nextjs-shadcn/app/account/orders/[id]/page.tsx +128 -0
- package/templates/storefront-nextjs-shadcn/app/account/orders/[id]/tracking/page.tsx +206 -0
- package/templates/storefront-nextjs-shadcn/app/account/orders/page.tsx +80 -0
- package/templates/storefront-nextjs-shadcn/app/account/page.tsx +107 -0
- package/templates/storefront-nextjs-shadcn/app/account/settings/page.tsx +195 -0
- package/templates/storefront-nextjs-shadcn/app/api/auth/clear-token/route.ts +87 -0
- package/templates/storefront-nextjs-shadcn/app/api/auth/set-token/route.ts +125 -0
- package/templates/storefront-nextjs-shadcn/app/auth/forgot-password/page.tsx +131 -0
- package/templates/storefront-nextjs-shadcn/app/auth/login/page.tsx +24 -0
- package/templates/storefront-nextjs-shadcn/app/auth/register/page.tsx +20 -0
- package/templates/storefront-nextjs-shadcn/app/blog/[slug]/page.tsx +323 -0
- package/templates/storefront-nextjs-shadcn/app/blog/page.tsx +159 -0
- package/templates/storefront-nextjs-shadcn/app/brands/[slug]/page.tsx +170 -0
- package/templates/storefront-nextjs-shadcn/app/brands/page.tsx +73 -0
- package/templates/storefront-nextjs-shadcn/app/cart/page.tsx +165 -0
- package/templates/storefront-nextjs-shadcn/app/categories/[slug]/page.tsx +78 -0
- package/templates/storefront-nextjs-shadcn/app/categories/page.tsx +75 -0
- package/templates/storefront-nextjs-shadcn/app/checkout/page.tsx +1752 -0
- package/templates/storefront-nextjs-shadcn/app/checkout/success/[orderId]/page.tsx +256 -0
- package/templates/storefront-nextjs-shadcn/app/collections/[handle]/page.tsx +74 -0
- package/templates/storefront-nextjs-shadcn/app/collections/page.tsx +75 -0
- package/templates/storefront-nextjs-shadcn/app/contact/page.tsx +114 -0
- package/templates/storefront-nextjs-shadcn/app/error.tsx +90 -0
- package/templates/storefront-nextjs-shadcn/app/globals.css +125 -0
- package/templates/storefront-nextjs-shadcn/app/layout.tsx +57 -0
- package/templates/storefront-nextjs-shadcn/app/not-found.tsx +68 -0
- package/templates/storefront-nextjs-shadcn/app/page.tsx +21 -0
- package/templates/storefront-nextjs-shadcn/app/products/[slug]/page.tsx +246 -0
- package/templates/storefront-nextjs-shadcn/app/products/[slug]/product-client.tsx +343 -0
- package/templates/storefront-nextjs-shadcn/app/products/page.tsx +25 -0
- package/templates/storefront-nextjs-shadcn/app/products/products-client.tsx +192 -0
- package/templates/storefront-nextjs-shadcn/app/returns/page.tsx +77 -0
- package/templates/storefront-nextjs-shadcn/app/robots.ts +53 -0
- package/templates/storefront-nextjs-shadcn/app/search/page.tsx +16 -0
- package/templates/storefront-nextjs-shadcn/app/search/search-client.tsx +47 -0
- package/templates/storefront-nextjs-shadcn/app/shipping/page.tsx +62 -0
- package/templates/storefront-nextjs-shadcn/app/sitemap.ts +144 -0
- package/templates/storefront-nextjs-shadcn/app/wishlist/page.tsx +179 -0
- package/templates/storefront-nextjs-shadcn/codegen.ts +51 -0
- package/templates/storefront-nextjs-shadcn/components/account/address-form.tsx +348 -0
- package/templates/storefront-nextjs-shadcn/components/account/address-list.tsx +144 -0
- package/templates/storefront-nextjs-shadcn/components/account/order-details.tsx +258 -0
- package/templates/storefront-nextjs-shadcn/components/account/order-history.tsx +107 -0
- package/templates/storefront-nextjs-shadcn/components/auth/account-menu.tsx +132 -0
- package/templates/storefront-nextjs-shadcn/components/auth/login-form.tsx +188 -0
- package/templates/storefront-nextjs-shadcn/components/auth/register-form.tsx +305 -0
- package/templates/storefront-nextjs-shadcn/components/blog/blog-card.tsx +240 -0
- package/templates/storefront-nextjs-shadcn/components/blog/blog-sidebar.tsx +177 -0
- package/templates/storefront-nextjs-shadcn/components/blog/index.ts +8 -0
- package/templates/storefront-nextjs-shadcn/components/brand/brand-card.tsx +119 -0
- package/templates/storefront-nextjs-shadcn/components/brand/brand-grid.tsx +64 -0
- package/templates/storefront-nextjs-shadcn/components/cart/cart-drawer.tsx +140 -0
- package/templates/storefront-nextjs-shadcn/components/cart/cart-icon.tsx +48 -0
- package/templates/storefront-nextjs-shadcn/components/cart/cart-item.tsx +112 -0
- package/templates/storefront-nextjs-shadcn/components/cart/cart-summary.tsx +84 -0
- package/templates/storefront-nextjs-shadcn/components/cart/index.ts +17 -0
- package/templates/storefront-nextjs-shadcn/components/cart/promo-code-input.tsx +121 -0
- package/templates/storefront-nextjs-shadcn/components/cart/shipping-estimator.tsx +162 -0
- package/templates/storefront-nextjs-shadcn/components/checkout/index.ts +25 -0
- package/templates/storefront-nextjs-shadcn/components/checkout/payment-method-card.tsx +187 -0
- package/templates/storefront-nextjs-shadcn/components/checkout/payment-step.tsx +160 -0
- package/templates/storefront-nextjs-shadcn/components/checkout/tax-breakdown.tsx +154 -0
- package/templates/storefront-nextjs-shadcn/components/commerce/currency-selector.tsx +225 -0
- package/templates/storefront-nextjs-shadcn/components/commerce/pagination.tsx +62 -0
- package/templates/storefront-nextjs-shadcn/components/commerce/product-actions.tsx +158 -0
- package/templates/storefront-nextjs-shadcn/components/commerce/search-input.tsx +174 -0
- package/templates/storefront-nextjs-shadcn/components/commerce/variant-selector.tsx +210 -0
- package/templates/storefront-nextjs-shadcn/components/common/category-card.tsx +97 -0
- package/templates/storefront-nextjs-shadcn/components/common/collection-card.tsx +187 -0
- package/templates/storefront-nextjs-shadcn/components/common/price-display.tsx +151 -0
- package/templates/storefront-nextjs-shadcn/components/common/social-share.tsx +166 -0
- package/templates/storefront-nextjs-shadcn/components/discount/discount-breakdown.tsx +245 -0
- package/templates/storefront-nextjs-shadcn/components/discount/discount-code-input.tsx +246 -0
- package/templates/storefront-nextjs-shadcn/components/discount/index.ts +19 -0
- package/templates/storefront-nextjs-shadcn/components/error/error-boundary.tsx +113 -0
- package/templates/storefront-nextjs-shadcn/components/error/index.ts +7 -0
- package/templates/storefront-nextjs-shadcn/components/filters/attribute-filter.tsx +153 -0
- package/templates/storefront-nextjs-shadcn/components/filters/checkbox-group-filter.tsx +167 -0
- package/templates/storefront-nextjs-shadcn/components/filters/color-swatch-filter.tsx +176 -0
- package/templates/storefront-nextjs-shadcn/components/filters/dynamic-attribute-filters.tsx +220 -0
- package/templates/storefront-nextjs-shadcn/components/filters/index.ts +36 -0
- package/templates/storefront-nextjs-shadcn/components/filters/range-slider-filter.tsx +193 -0
- package/templates/storefront-nextjs-shadcn/components/filters/toggle-filter.tsx +132 -0
- package/templates/storefront-nextjs-shadcn/components/gift-card/gift-card-balance.tsx +321 -0
- package/templates/storefront-nextjs-shadcn/components/gift-card/gift-card-input.tsx +309 -0
- package/templates/storefront-nextjs-shadcn/components/gift-card/index.ts +24 -0
- package/templates/storefront-nextjs-shadcn/components/home/category-grid.tsx +72 -0
- package/templates/storefront-nextjs-shadcn/components/home/featured-collections.tsx +107 -0
- package/templates/storefront-nextjs-shadcn/components/home/featured-products.tsx +85 -0
- package/templates/storefront-nextjs-shadcn/components/home/hero-section.tsx +34 -0
- package/templates/storefront-nextjs-shadcn/components/home/index.ts +8 -0
- package/templates/storefront-nextjs-shadcn/components/home/newsletter-signup.tsx +108 -0
- package/templates/storefront-nextjs-shadcn/components/layout/breadcrumbs.tsx +133 -0
- package/templates/storefront-nextjs-shadcn/components/layout/currency-selector.tsx +341 -0
- package/templates/storefront-nextjs-shadcn/components/layout/footer.tsx +128 -0
- package/templates/storefront-nextjs-shadcn/components/layout/header.tsx +147 -0
- package/templates/storefront-nextjs-shadcn/components/layout/index.ts +9 -0
- package/templates/storefront-nextjs-shadcn/components/layout/mobile-menu.tsx +211 -0
- package/templates/storefront-nextjs-shadcn/components/layout/navigation.tsx +95 -0
- package/templates/storefront-nextjs-shadcn/components/layout/theme-switcher.tsx +192 -0
- package/templates/storefront-nextjs-shadcn/components/loyalty/index.ts +11 -0
- package/templates/storefront-nextjs-shadcn/components/loyalty/points-balance.tsx +93 -0
- package/templates/storefront-nextjs-shadcn/components/loyalty/points-history.tsx +177 -0
- package/templates/storefront-nextjs-shadcn/components/loyalty/referral-section.tsx +250 -0
- package/templates/storefront-nextjs-shadcn/components/loyalty/rewards-catalog.tsx +217 -0
- package/templates/storefront-nextjs-shadcn/components/loyalty/tier-badge.tsx +106 -0
- package/templates/storefront-nextjs-shadcn/components/loyalty/tier-progress.tsx +131 -0
- package/templates/storefront-nextjs-shadcn/components/order/delivery-estimate.tsx +196 -0
- package/templates/storefront-nextjs-shadcn/components/order/index.ts +11 -0
- package/templates/storefront-nextjs-shadcn/components/order/order-tracking.tsx +200 -0
- package/templates/storefront-nextjs-shadcn/components/order/shipment-card.tsx +407 -0
- package/templates/storefront-nextjs-shadcn/components/order/tracking-status.tsx +222 -0
- package/templates/storefront-nextjs-shadcn/components/order/tracking-timeline.tsx +205 -0
- package/templates/storefront-nextjs-shadcn/components/product/add-to-cart-button.tsx +161 -0
- package/templates/storefront-nextjs-shadcn/components/product/b2b-price-display.tsx +250 -0
- package/templates/storefront-nextjs-shadcn/components/product/discount-badge.tsx +196 -0
- package/templates/storefront-nextjs-shadcn/components/product/index.ts +41 -0
- package/templates/storefront-nextjs-shadcn/components/product/product-card.tsx +147 -0
- package/templates/storefront-nextjs-shadcn/components/product/product-filters.tsx +217 -0
- package/templates/storefront-nextjs-shadcn/components/product/product-gallery.tsx +143 -0
- package/templates/storefront-nextjs-shadcn/components/product/product-grid.tsx +83 -0
- package/templates/storefront-nextjs-shadcn/components/product/product-image.tsx +155 -0
- package/templates/storefront-nextjs-shadcn/components/product/product-price.tsx +158 -0
- package/templates/storefront-nextjs-shadcn/components/product/product-quantity-selector.tsx +111 -0
- package/templates/storefront-nextjs-shadcn/components/product/product-reviews.tsx +238 -0
- package/templates/storefront-nextjs-shadcn/components/product/product-sort.tsx +58 -0
- package/templates/storefront-nextjs-shadcn/components/product/product-variant-selector.tsx +169 -0
- package/templates/storefront-nextjs-shadcn/components/product/review-card.tsx +220 -0
- package/templates/storefront-nextjs-shadcn/components/product/review-form.tsx +338 -0
- package/templates/storefront-nextjs-shadcn/components/product/review-summary.tsx +143 -0
- package/templates/storefront-nextjs-shadcn/components/product/sale-countdown.tsx +166 -0
- package/templates/storefront-nextjs-shadcn/components/product/savings-display.tsx +213 -0
- package/templates/storefront-nextjs-shadcn/components/product/similar-products.tsx +57 -0
- package/templates/storefront-nextjs-shadcn/components/product/stock-indicator.tsx +91 -0
- package/templates/storefront-nextjs-shadcn/components/providers/currency-provider.tsx +103 -0
- package/templates/storefront-nextjs-shadcn/components/providers/index.ts +8 -0
- package/templates/storefront-nextjs-shadcn/components/providers/query-provider.tsx +260 -0
- package/templates/storefront-nextjs-shadcn/components/providers/theme-provider.tsx +13 -0
- package/templates/storefront-nextjs-shadcn/components/returns/index.ts +26 -0
- package/templates/storefront-nextjs-shadcn/components/returns/return-request-form.tsx +608 -0
- package/templates/storefront-nextjs-shadcn/components/returns/return-status-card.tsx +554 -0
- package/templates/storefront-nextjs-shadcn/components/search/index.ts +8 -0
- package/templates/storefront-nextjs-shadcn/components/search/search-bar.tsx +140 -0
- package/templates/storefront-nextjs-shadcn/components/search/search-results.tsx +58 -0
- package/templates/storefront-nextjs-shadcn/components/search/search-suggestions.tsx +43 -0
- package/templates/storefront-nextjs-shadcn/components/seo/index.ts +12 -0
- package/templates/storefront-nextjs-shadcn/components/seo/json-ld.tsx +56 -0
- package/templates/storefront-nextjs-shadcn/components/seo/product-json-ld.ts +167 -0
- package/templates/storefront-nextjs-shadcn/components/shipping/index.ts +16 -0
- package/templates/storefront-nextjs-shadcn/components/shipping/shipping-method-selector.tsx +337 -0
- package/templates/storefront-nextjs-shadcn/components/ui/accordion.tsx +153 -0
- package/templates/storefront-nextjs-shadcn/components/ui/alert.tsx +59 -0
- package/templates/storefront-nextjs-shadcn/components/ui/badge.tsx +34 -0
- package/templates/storefront-nextjs-shadcn/components/ui/button.tsx +51 -0
- package/templates/storefront-nextjs-shadcn/components/ui/card.tsx +77 -0
- package/templates/storefront-nextjs-shadcn/components/ui/checkbox.tsx +30 -0
- package/templates/storefront-nextjs-shadcn/components/ui/dialog.tsx +137 -0
- package/templates/storefront-nextjs-shadcn/components/ui/empty-state.tsx +207 -0
- package/templates/storefront-nextjs-shadcn/components/ui/index.ts +67 -0
- package/templates/storefront-nextjs-shadcn/components/ui/input.tsx +65 -0
- package/templates/storefront-nextjs-shadcn/components/ui/label.tsx +26 -0
- package/templates/storefront-nextjs-shadcn/components/ui/pagination.tsx +205 -0
- package/templates/storefront-nextjs-shadcn/components/ui/radio-group.tsx +44 -0
- package/templates/storefront-nextjs-shadcn/components/ui/select.tsx +160 -0
- package/templates/storefront-nextjs-shadcn/components/ui/separator.tsx +28 -0
- package/templates/storefront-nextjs-shadcn/components/ui/skeleton.tsx +20 -0
- package/templates/storefront-nextjs-shadcn/components/ui/spinner.tsx +82 -0
- package/templates/storefront-nextjs-shadcn/components/ui/tabs.tsx +119 -0
- package/templates/storefront-nextjs-shadcn/components/ui/toast.tsx +96 -0
- package/templates/storefront-nextjs-shadcn/components/wishlist/index.ts +9 -0
- package/templates/storefront-nextjs-shadcn/components/wishlist/wishlist-button.tsx +148 -0
- package/templates/storefront-nextjs-shadcn/components/wishlist/wishlist-icon.tsx +47 -0
- package/templates/storefront-nextjs-shadcn/components/wishlist/wishlist-item.tsx +165 -0
- package/templates/storefront-nextjs-shadcn/components.json +19 -0
- package/templates/storefront-nextjs-shadcn/generated/.gitkeep +2 -0
- package/templates/storefront-nextjs-shadcn/graphql/.gitkeep +31 -0
- package/templates/storefront-nextjs-shadcn/graphql/collections.example.ts +168 -0
- package/templates/storefront-nextjs-shadcn/graphql/products.example.ts +160 -0
- package/templates/storefront-nextjs-shadcn/hooks/index.ts +9 -0
- package/templates/storefront-nextjs-shadcn/hooks/use-auth.ts +310 -0
- package/templates/storefront-nextjs-shadcn/hooks/use-cart-actions.ts +286 -0
- package/templates/storefront-nextjs-shadcn/hooks/use-cart-sync.ts +110 -0
- package/templates/storefront-nextjs-shadcn/hooks/use-filter-params.test.ts +173 -0
- package/templates/storefront-nextjs-shadcn/hooks/use-filter-params.ts +298 -0
- package/templates/storefront-nextjs-shadcn/lib/auth/cookies.ts +220 -0
- package/templates/storefront-nextjs-shadcn/lib/auth/routes.ts +57 -0
- package/templates/storefront-nextjs-shadcn/lib/config.ts +46 -0
- package/templates/storefront-nextjs-shadcn/lib/currency/IMPLEMENTATION_SUMMARY.md +254 -0
- package/templates/storefront-nextjs-shadcn/lib/currency/README.md +464 -0
- package/templates/storefront-nextjs-shadcn/lib/currency/cookie-manager.test.ts +328 -0
- package/templates/storefront-nextjs-shadcn/lib/currency/cookie-manager.ts +295 -0
- package/templates/storefront-nextjs-shadcn/lib/currency/index.ts +27 -0
- package/templates/storefront-nextjs-shadcn/lib/format.test.ts +397 -0
- package/templates/storefront-nextjs-shadcn/lib/format.ts +226 -0
- package/templates/storefront-nextjs-shadcn/lib/graphql/client.ts +109 -0
- package/templates/storefront-nextjs-shadcn/lib/graphql/hooks.ts +1183 -0
- package/templates/storefront-nextjs-shadcn/lib/graphql/server.ts +267 -0
- package/templates/storefront-nextjs-shadcn/lib/hooks.ts +30 -0
- package/templates/storefront-nextjs-shadcn/lib/theme/theme-config.ts +89 -0
- package/templates/storefront-nextjs-shadcn/lib/utils.ts +6 -0
- package/templates/storefront-nextjs-shadcn/next.config.ts +47 -0
- package/templates/storefront-nextjs-shadcn/open-next.config.ts +3 -0
- package/templates/storefront-nextjs-shadcn/package.dev.json +30 -0
- package/templates/storefront-nextjs-shadcn/package.json +60 -0
- package/templates/storefront-nextjs-shadcn/package.json.template +46 -0
- package/templates/storefront-nextjs-shadcn/postcss.config.mjs +8 -0
- package/templates/storefront-nextjs-shadcn/proxy.ts +80 -0
- package/templates/storefront-nextjs-shadcn/public/icons/payment/apple-pay.svg +8 -0
- package/templates/storefront-nextjs-shadcn/public/icons/payment/bank-transfer.svg +10 -0
- package/templates/storefront-nextjs-shadcn/public/icons/payment/blik.svg +6 -0
- package/templates/storefront-nextjs-shadcn/public/icons/payment/cash-on-delivery.svg +11 -0
- package/templates/storefront-nextjs-shadcn/public/icons/payment/google-pay.svg +11 -0
- package/templates/storefront-nextjs-shadcn/public/icons/payment/mastercard.svg +7 -0
- package/templates/storefront-nextjs-shadcn/public/icons/payment/paypal.svg +7 -0
- package/templates/storefront-nextjs-shadcn/public/icons/payment/payu.svg +7 -0
- package/templates/storefront-nextjs-shadcn/public/icons/payment/przelewy24.svg +7 -0
- package/templates/storefront-nextjs-shadcn/public/icons/payment/stripe.svg +4 -0
- package/templates/storefront-nextjs-shadcn/public/icons/payment/visa.svg +5 -0
- package/templates/storefront-nextjs-shadcn/stores/auth-store.ts +66 -0
- package/templates/storefront-nextjs-shadcn/stores/cart-store.ts +56 -0
- package/templates/storefront-nextjs-shadcn/stores/checkout-store.ts +184 -0
- package/templates/storefront-nextjs-shadcn/stores/currency-store.ts +103 -0
- package/templates/storefront-nextjs-shadcn/stores/wishlist-store.ts +291 -0
- package/templates/storefront-nextjs-shadcn/tailwind.config.ts +111 -0
- package/templates/storefront-nextjs-shadcn/tsconfig.json +27 -0
- package/templates/storefront-nextjs-shadcn/wrangler.toml +9 -0
|
@@ -0,0 +1,1028 @@
|
|
|
1
|
+
import * as p from "@clack/prompts";
|
|
2
|
+
import pc from "picocolors";
|
|
3
|
+
import ora from "ora";
|
|
4
|
+
import { existsSync, writeFileSync, mkdirSync, readFileSync, readdirSync, statSync, } from "fs";
|
|
5
|
+
import { join, dirname, basename } from "path";
|
|
6
|
+
import { fileURLToPath } from "url";
|
|
7
|
+
import { execSync, execFileSync } from "child_process";
|
|
8
|
+
import { detectPackageManager, getPackageManagerInfo, installDependencies, } from "../lib/package-manager.js";
|
|
9
|
+
import { getStoredToken } from "./auth.js";
|
|
10
|
+
import { hasProfiles, addProfile } from "../lib/env-storage.js";
|
|
11
|
+
import { getApiUrl } from "../lib/api-url.js";
|
|
12
|
+
import { WizardEngine, selectWithBack, isBack, isCancel, next, back, cancel, clearScreen, } from "../lib/wizard-engine.js";
|
|
13
|
+
import { t, initI18n } from "../lib/i18n.js";
|
|
14
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
15
|
+
const __dirname = dirname(__filename);
|
|
16
|
+
// Initialize i18n
|
|
17
|
+
initI18n();
|
|
18
|
+
// ============================================================================
|
|
19
|
+
// API Functions
|
|
20
|
+
// ============================================================================
|
|
21
|
+
/**
|
|
22
|
+
* Fetch user's teams from CLI API
|
|
23
|
+
*/
|
|
24
|
+
async function fetchTeams(apiUrl, token) {
|
|
25
|
+
try {
|
|
26
|
+
const response = await fetch(`${apiUrl}/cli/teams`, {
|
|
27
|
+
headers: {
|
|
28
|
+
Authorization: `Bearer ${token}`,
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
if (!response.ok) {
|
|
32
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
33
|
+
}
|
|
34
|
+
const data = (await response.json());
|
|
35
|
+
return data.teams || [];
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
p.log.warn(t().failedFetchTeams(error.message));
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Fetch user's projects from CLI API
|
|
44
|
+
*/
|
|
45
|
+
async function fetchProjects(apiUrl, token) {
|
|
46
|
+
try {
|
|
47
|
+
const response = await fetch(`${apiUrl}/cli/projects`, {
|
|
48
|
+
headers: {
|
|
49
|
+
Authorization: `Bearer ${token}`,
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
if (!response.ok) {
|
|
53
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
54
|
+
}
|
|
55
|
+
const data = (await response.json());
|
|
56
|
+
return data.projects || [];
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
p.log.warn(t().failedFetchProjects(error.message));
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Fetch templates from the remote registry API.
|
|
65
|
+
*/
|
|
66
|
+
async function fetchRemoteTemplates(apiUrl, token) {
|
|
67
|
+
try {
|
|
68
|
+
const response = await fetch(`${apiUrl}/cli/templates`, {
|
|
69
|
+
headers: {
|
|
70
|
+
Authorization: `Bearer ${token}`,
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
if (!response.ok) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
const data = (await response.json());
|
|
77
|
+
return data.filter((tpl) => tpl.status === "PUBLISHED");
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Download a remote template using degit.
|
|
85
|
+
*/
|
|
86
|
+
async function downloadTemplate(repoUrl, targetDir) {
|
|
87
|
+
// Validate the repo URL to prevent path traversal and restrict to trusted hosts
|
|
88
|
+
const allowedHosts = ['github.com', 'gitlab.com', 'bitbucket.org'];
|
|
89
|
+
try {
|
|
90
|
+
const parsed = new URL(repoUrl.startsWith('http') ? repoUrl : `https://github.com/${repoUrl}`);
|
|
91
|
+
if (!allowedHosts.includes(parsed.hostname)) {
|
|
92
|
+
throw new Error(`Untrusted repository host: ${parsed.hostname}. Allowed: ${allowedHosts.join(', ')}`);
|
|
93
|
+
}
|
|
94
|
+
if (parsed.pathname.includes('..')) {
|
|
95
|
+
throw new Error('Path traversal detected in repository URL');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch (e) {
|
|
99
|
+
if (e instanceof Error && (e.message.includes('Untrusted') || e.message.includes('traversal')))
|
|
100
|
+
throw e;
|
|
101
|
+
// degit supports shorthand like "user/repo" — validate format
|
|
102
|
+
if (!/^[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+(?:#[a-zA-Z0-9_.-]+)?$/.test(repoUrl)) {
|
|
103
|
+
throw new Error(`Invalid repository URL format: ${repoUrl}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const { default: degit } = await import("degit");
|
|
107
|
+
const emitter = degit(repoUrl, { cache: false, force: true });
|
|
108
|
+
await emitter.clone(targetDir);
|
|
109
|
+
}
|
|
110
|
+
// ============================================================================
|
|
111
|
+
// Template Utilities
|
|
112
|
+
// ============================================================================
|
|
113
|
+
/**
|
|
114
|
+
* Apply placeholder replacements to all files in a directory.
|
|
115
|
+
*/
|
|
116
|
+
function applyReplacementsToDirectory(dir, replacements) {
|
|
117
|
+
const textExtensions = new Set([
|
|
118
|
+
".ts",
|
|
119
|
+
".tsx",
|
|
120
|
+
".js",
|
|
121
|
+
".jsx",
|
|
122
|
+
".json",
|
|
123
|
+
".yaml",
|
|
124
|
+
".yml",
|
|
125
|
+
".md",
|
|
126
|
+
".mdx",
|
|
127
|
+
".html",
|
|
128
|
+
".css",
|
|
129
|
+
".env",
|
|
130
|
+
".env.example",
|
|
131
|
+
".env.local",
|
|
132
|
+
".toml",
|
|
133
|
+
".cfg",
|
|
134
|
+
]);
|
|
135
|
+
const entries = readdirSync(dir);
|
|
136
|
+
for (const entry of entries) {
|
|
137
|
+
const fullPath = join(dir, entry);
|
|
138
|
+
const stat = statSync(fullPath);
|
|
139
|
+
if (stat.isDirectory()) {
|
|
140
|
+
if (entry === "node_modules" || entry === ".git")
|
|
141
|
+
continue;
|
|
142
|
+
applyReplacementsToDirectory(fullPath, replacements);
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
const ext = entry.includes(".") ? "." + entry.split(".").pop() : "";
|
|
146
|
+
const isTextFile = textExtensions.has(ext) || entry.startsWith(".env");
|
|
147
|
+
if (!isTextFile)
|
|
148
|
+
continue;
|
|
149
|
+
try {
|
|
150
|
+
let content = readFileSync(fullPath, "utf-8");
|
|
151
|
+
let changed = false;
|
|
152
|
+
for (const [placeholder, value] of Object.entries(replacements)) {
|
|
153
|
+
const regex = new RegExp(placeholder, "g");
|
|
154
|
+
const newContent = content.replace(regex, value);
|
|
155
|
+
if (newContent !== content) {
|
|
156
|
+
content = newContent;
|
|
157
|
+
changed = true;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (changed) {
|
|
161
|
+
writeFileSync(fullPath, content);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
// Skip binary files or files that can't be read as text
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Recursively copy directory with placeholder replacement.
|
|
172
|
+
*/
|
|
173
|
+
function copyTemplateDir(src, dest, replacements) {
|
|
174
|
+
mkdirSync(dest, { recursive: true });
|
|
175
|
+
const entries = readdirSync(src);
|
|
176
|
+
for (const entry of entries) {
|
|
177
|
+
const srcPath = join(src, entry);
|
|
178
|
+
const destPath = join(dest, entry);
|
|
179
|
+
const stat = statSync(srcPath);
|
|
180
|
+
if (stat.isDirectory()) {
|
|
181
|
+
copyTemplateDir(srcPath, destPath, replacements);
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
let content = readFileSync(srcPath, "utf-8");
|
|
185
|
+
for (const [placeholder, value] of Object.entries(replacements)) {
|
|
186
|
+
content = content.replace(new RegExp(placeholder, "g"), value);
|
|
187
|
+
}
|
|
188
|
+
writeFileSync(destPath, content);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Initialize a git repository in the scaffolded project.
|
|
194
|
+
*/
|
|
195
|
+
async function initGitRepository(projectPath, templateName) {
|
|
196
|
+
try {
|
|
197
|
+
// Check if git is available
|
|
198
|
+
try {
|
|
199
|
+
execSync("git --version", { stdio: "pipe" });
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
// Skip if already in a git repo
|
|
205
|
+
try {
|
|
206
|
+
execSync("git rev-parse --git-dir", { stdio: "pipe", cwd: projectPath });
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
// Not in a git repo - proceed
|
|
211
|
+
}
|
|
212
|
+
execFileSync("git", ["init"], { stdio: "pipe", cwd: projectPath });
|
|
213
|
+
execFileSync("git", ["add", "-A"], { stdio: "pipe", cwd: projectPath });
|
|
214
|
+
execFileSync("git", ["commit", "-m", `chore: scaffold ${templateName} with DoSwiftly CLI`], {
|
|
215
|
+
stdio: "pipe",
|
|
216
|
+
cwd: projectPath,
|
|
217
|
+
});
|
|
218
|
+
p.log.info(pc.dim("Git repository initialized with initial commit."));
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
// Non-fatal
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
// ============================================================================
|
|
225
|
+
// File Creation Utilities
|
|
226
|
+
// ============================================================================
|
|
227
|
+
/**
|
|
228
|
+
* Create .env.local file
|
|
229
|
+
*/
|
|
230
|
+
function createEnvLocal(targetDir, shopSlug, apiUrl, projectName) {
|
|
231
|
+
const envContent = [
|
|
232
|
+
"# Generated by DoSwiftly CLI",
|
|
233
|
+
`# Project: ${projectName}`,
|
|
234
|
+
"",
|
|
235
|
+
`NEXT_PUBLIC_API_URL=${apiUrl}`,
|
|
236
|
+
`NEXT_PUBLIC_SHOP_SLUG=${shopSlug}`,
|
|
237
|
+
"",
|
|
238
|
+
"# Site Configuration",
|
|
239
|
+
"NEXT_PUBLIC_SITE_URL=http://localhost:3000",
|
|
240
|
+
`NEXT_PUBLIC_SITE_NAME=${projectName}`,
|
|
241
|
+
"",
|
|
242
|
+
].join("\n");
|
|
243
|
+
writeFileSync(join(targetDir, ".env.local"), envContent);
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Create doswiftly.config.ts file
|
|
247
|
+
*/
|
|
248
|
+
function createDoswiftlyConfig(targetDir, shopSlug, apiUrl, projectName) {
|
|
249
|
+
const configContent = [
|
|
250
|
+
"// Storefront configuration",
|
|
251
|
+
"// This file is used by both CLI and runtime code",
|
|
252
|
+
"",
|
|
253
|
+
"export interface DoswiftlyConfig {",
|
|
254
|
+
" shop: {",
|
|
255
|
+
" slug: string;",
|
|
256
|
+
" };",
|
|
257
|
+
" project: {",
|
|
258
|
+
" name: string;",
|
|
259
|
+
" };",
|
|
260
|
+
" api: {",
|
|
261
|
+
" url: string;",
|
|
262
|
+
" };",
|
|
263
|
+
" dev?: {",
|
|
264
|
+
" port?: number;",
|
|
265
|
+
" openBrowser?: boolean;",
|
|
266
|
+
" };",
|
|
267
|
+
"}",
|
|
268
|
+
"",
|
|
269
|
+
"const config: DoswiftlyConfig = {",
|
|
270
|
+
" shop: {",
|
|
271
|
+
` slug: '${shopSlug}',`,
|
|
272
|
+
" },",
|
|
273
|
+
" project: {",
|
|
274
|
+
` name: '${projectName}',`,
|
|
275
|
+
" },",
|
|
276
|
+
" api: {",
|
|
277
|
+
` url: '${apiUrl}',`,
|
|
278
|
+
" },",
|
|
279
|
+
" dev: {",
|
|
280
|
+
" port: 3000,",
|
|
281
|
+
" openBrowser: true,",
|
|
282
|
+
" },",
|
|
283
|
+
"};",
|
|
284
|
+
"",
|
|
285
|
+
"export default config;",
|
|
286
|
+
"",
|
|
287
|
+
].join("\n");
|
|
288
|
+
writeFileSync(join(targetDir, "doswiftly.config.ts"), configContent);
|
|
289
|
+
}
|
|
290
|
+
// ============================================================================
|
|
291
|
+
// Wizard Steps
|
|
292
|
+
// ============================================================================
|
|
293
|
+
/**
|
|
294
|
+
* Step 1: Target directory selection
|
|
295
|
+
*/
|
|
296
|
+
const stepDirectory = {
|
|
297
|
+
id: "directory",
|
|
298
|
+
title: () => t().targetDirectory,
|
|
299
|
+
stateKeys: ["targetDir", "projectName"],
|
|
300
|
+
canGoBack: false, // First step, can't go back
|
|
301
|
+
async execute(state, ctx) {
|
|
302
|
+
// Use a loop to handle validation errors without losing context
|
|
303
|
+
while (true) {
|
|
304
|
+
const choice = await p.select({
|
|
305
|
+
message: t().targetDirectory,
|
|
306
|
+
options: [
|
|
307
|
+
{
|
|
308
|
+
value: "current",
|
|
309
|
+
label: t().currentDirectory(basename(process.cwd())),
|
|
310
|
+
},
|
|
311
|
+
{ value: "new", label: t().newDirectory },
|
|
312
|
+
],
|
|
313
|
+
});
|
|
314
|
+
if (p.isCancel(choice))
|
|
315
|
+
return cancel();
|
|
316
|
+
if (choice === "current") {
|
|
317
|
+
const cwd = process.cwd();
|
|
318
|
+
const projectName = basename(cwd);
|
|
319
|
+
// Validate: directory should be empty or contain only hidden files
|
|
320
|
+
const entries = readdirSync(cwd).filter((e) => !e.startsWith("."));
|
|
321
|
+
if (entries.length > 0) {
|
|
322
|
+
// Clear screen and show error with option to retry
|
|
323
|
+
clearScreen();
|
|
324
|
+
p.intro(pc.bgCyan(pc.black(` ${t().wizardTitle} `)));
|
|
325
|
+
const current = ctx.stepIndex + 1;
|
|
326
|
+
const total = ctx.totalSteps;
|
|
327
|
+
const filled = pc.green('●'.repeat(current));
|
|
328
|
+
const empty = pc.dim('○'.repeat(total - current));
|
|
329
|
+
p.log.step(pc.cyan(`${t().stepOf(current, total)}: ${t().targetDirectory}`));
|
|
330
|
+
p.log.message(`${filled}${empty}`, { symbol: '' });
|
|
331
|
+
p.log.error(t().directoryNotEmpty);
|
|
332
|
+
p.log.info(pc.dim(t().chooseNewDirectory));
|
|
333
|
+
const retry = await p.confirm({
|
|
334
|
+
message: t().tryAgain,
|
|
335
|
+
initialValue: true,
|
|
336
|
+
});
|
|
337
|
+
if (p.isCancel(retry))
|
|
338
|
+
return cancel();
|
|
339
|
+
if (!retry)
|
|
340
|
+
return cancel();
|
|
341
|
+
// Clear screen and redraw for retry
|
|
342
|
+
clearScreen();
|
|
343
|
+
p.intro(pc.bgCyan(pc.black(` ${t().wizardTitle} `)));
|
|
344
|
+
p.log.step(pc.cyan(`${t().stepOf(current, total)}: ${t().targetDirectory}`));
|
|
345
|
+
p.log.message(`${filled}${empty}`, { symbol: '' });
|
|
346
|
+
continue; // Loop back to selection
|
|
347
|
+
}
|
|
348
|
+
// Check if already initialized
|
|
349
|
+
if (existsSync(join(cwd, "doswiftly.config.ts")) ||
|
|
350
|
+
existsSync(join(cwd, "doswiftly.config.js"))) {
|
|
351
|
+
// Clear screen and show error
|
|
352
|
+
clearScreen();
|
|
353
|
+
p.intro(pc.bgCyan(pc.black(` ${t().wizardTitle} `)));
|
|
354
|
+
const current = ctx.stepIndex + 1;
|
|
355
|
+
const total = ctx.totalSteps;
|
|
356
|
+
const filled = pc.green('●'.repeat(current));
|
|
357
|
+
const empty = pc.dim('○'.repeat(total - current));
|
|
358
|
+
p.log.step(pc.cyan(`${t().stepOf(current, total)}: ${t().targetDirectory}`));
|
|
359
|
+
p.log.message(`${filled}${empty}`, { symbol: '' });
|
|
360
|
+
p.log.error(t().alreadyInitialized);
|
|
361
|
+
p.log.info(pc.dim(t().useDeploy));
|
|
362
|
+
const retry = await p.confirm({
|
|
363
|
+
message: t().tryAgain,
|
|
364
|
+
initialValue: true,
|
|
365
|
+
});
|
|
366
|
+
if (p.isCancel(retry))
|
|
367
|
+
return cancel();
|
|
368
|
+
if (!retry)
|
|
369
|
+
return cancel();
|
|
370
|
+
// Clear screen and redraw for retry
|
|
371
|
+
clearScreen();
|
|
372
|
+
p.intro(pc.bgCyan(pc.black(` ${t().wizardTitle} `)));
|
|
373
|
+
p.log.step(pc.cyan(`${t().stepOf(current, total)}: ${t().targetDirectory}`));
|
|
374
|
+
p.log.message(`${filled}${empty}`, { symbol: '' });
|
|
375
|
+
continue; // Loop back to selection
|
|
376
|
+
}
|
|
377
|
+
return next({ targetDir: cwd, projectName });
|
|
378
|
+
}
|
|
379
|
+
// "new" was selected, break out of loop
|
|
380
|
+
break;
|
|
381
|
+
}
|
|
382
|
+
// New directory flow
|
|
383
|
+
const projectName = await p.text({
|
|
384
|
+
message: t().directoryName,
|
|
385
|
+
validate: (v) => {
|
|
386
|
+
if (!v)
|
|
387
|
+
return t().nameRequired;
|
|
388
|
+
if (!/^[a-z0-9-]+$/.test(v))
|
|
389
|
+
return t().invalidName;
|
|
390
|
+
if (existsSync(join(process.cwd(), v)))
|
|
391
|
+
return t().directoryExists;
|
|
392
|
+
return undefined;
|
|
393
|
+
},
|
|
394
|
+
});
|
|
395
|
+
if (p.isCancel(projectName))
|
|
396
|
+
return cancel();
|
|
397
|
+
return next({
|
|
398
|
+
targetDir: join(process.cwd(), projectName),
|
|
399
|
+
projectName: projectName,
|
|
400
|
+
});
|
|
401
|
+
},
|
|
402
|
+
};
|
|
403
|
+
/**
|
|
404
|
+
* Step 2: Team selection
|
|
405
|
+
*/
|
|
406
|
+
const stepTeam = {
|
|
407
|
+
id: "team",
|
|
408
|
+
title: () => t().selectTeam,
|
|
409
|
+
stateKeys: ["team"],
|
|
410
|
+
canGoBack: true,
|
|
411
|
+
async execute(state, ctx) {
|
|
412
|
+
const teams = state.__teams;
|
|
413
|
+
// Single team: ask for confirmation instead of auto-selecting
|
|
414
|
+
if (teams.length === 1) {
|
|
415
|
+
const proceed = await p.confirm({
|
|
416
|
+
message: t().useTeam(teams[0].name),
|
|
417
|
+
initialValue: true,
|
|
418
|
+
});
|
|
419
|
+
if (p.isCancel(proceed))
|
|
420
|
+
return cancel();
|
|
421
|
+
if (!proceed)
|
|
422
|
+
return back();
|
|
423
|
+
return next({ team: teams[0] });
|
|
424
|
+
}
|
|
425
|
+
// Multiple teams: show select with back navigation
|
|
426
|
+
const teamId = await selectWithBack({
|
|
427
|
+
message: t().selectTeam,
|
|
428
|
+
options: teams.map((team) => ({
|
|
429
|
+
value: team.id,
|
|
430
|
+
label: team.name,
|
|
431
|
+
hint: t().teamProjects(team.projectCount),
|
|
432
|
+
})),
|
|
433
|
+
canGoBack: ctx.canGoBack,
|
|
434
|
+
});
|
|
435
|
+
if (isCancel(teamId))
|
|
436
|
+
return cancel();
|
|
437
|
+
if (isBack(teamId))
|
|
438
|
+
return back();
|
|
439
|
+
const selectedTeam = teams.find((team) => team.id === teamId);
|
|
440
|
+
return next({ team: selectedTeam });
|
|
441
|
+
},
|
|
442
|
+
};
|
|
443
|
+
/**
|
|
444
|
+
* Step 3: Project selection
|
|
445
|
+
*/
|
|
446
|
+
const stepProject = {
|
|
447
|
+
id: "project",
|
|
448
|
+
title: () => t().selectProject,
|
|
449
|
+
stateKeys: ["project", "shopSlug"],
|
|
450
|
+
canGoBack: true,
|
|
451
|
+
async execute(state, ctx) {
|
|
452
|
+
const teamProjects = state.__projects.filter((p) => p.teamId === state.team.id);
|
|
453
|
+
if (teamProjects.length === 0) {
|
|
454
|
+
// Clear screen and redraw header for consistent UI
|
|
455
|
+
clearScreen();
|
|
456
|
+
p.intro(pc.bgCyan(pc.black(` ${t().wizardTitle} `)));
|
|
457
|
+
// Show progress
|
|
458
|
+
const current = ctx.stepIndex + 1;
|
|
459
|
+
const total = ctx.totalSteps;
|
|
460
|
+
const filled = pc.green('●'.repeat(current));
|
|
461
|
+
const empty = pc.dim('○'.repeat(total - current));
|
|
462
|
+
p.log.step(pc.cyan(`${t().stepOf(current, total)}: ${t().selectProject}`));
|
|
463
|
+
p.log.message(`${filled}${empty}`, { symbol: '' });
|
|
464
|
+
p.log.error(t().noProjects);
|
|
465
|
+
p.log.info(pc.cyan(t().createProjectHint));
|
|
466
|
+
// Allow going back to select different team
|
|
467
|
+
const goBack = await p.confirm({
|
|
468
|
+
message: t().goBackToTeam,
|
|
469
|
+
initialValue: true,
|
|
470
|
+
});
|
|
471
|
+
if (p.isCancel(goBack))
|
|
472
|
+
return cancel();
|
|
473
|
+
if (goBack)
|
|
474
|
+
return back();
|
|
475
|
+
// User chose not to go back
|
|
476
|
+
return cancel();
|
|
477
|
+
}
|
|
478
|
+
const projectId = await selectWithBack({
|
|
479
|
+
message: t().selectProject,
|
|
480
|
+
options: teamProjects.map((proj) => ({
|
|
481
|
+
value: proj.id,
|
|
482
|
+
label: proj.name,
|
|
483
|
+
hint: proj.hasShop ? t().shopLabel(proj.shop.slug) : t().noShop,
|
|
484
|
+
})),
|
|
485
|
+
canGoBack: ctx.canGoBack,
|
|
486
|
+
});
|
|
487
|
+
if (isCancel(projectId))
|
|
488
|
+
return cancel();
|
|
489
|
+
if (isBack(projectId))
|
|
490
|
+
return back();
|
|
491
|
+
const selectedProject = teamProjects.find((p) => p.id === projectId);
|
|
492
|
+
// Validate shop exists
|
|
493
|
+
if (!selectedProject?.hasShop || !selectedProject.shop) {
|
|
494
|
+
// Clear screen and redraw header for consistent UI
|
|
495
|
+
clearScreen();
|
|
496
|
+
p.intro(pc.bgCyan(pc.black(` ${t().wizardTitle} `)));
|
|
497
|
+
// Show progress
|
|
498
|
+
const current = ctx.stepIndex + 1;
|
|
499
|
+
const total = ctx.totalSteps;
|
|
500
|
+
const filled = pc.green('●'.repeat(current));
|
|
501
|
+
const empty = pc.dim('○'.repeat(total - current));
|
|
502
|
+
p.log.step(pc.cyan(`${t().stepOf(current, total)}: ${t().selectProject}`));
|
|
503
|
+
p.log.message(`${filled}${empty}`, { symbol: '' });
|
|
504
|
+
p.log.error(t().projectNoShop);
|
|
505
|
+
p.log.info(pc.cyan(t().createShopHint(selectedProject.id)));
|
|
506
|
+
// Allow going back instead of exiting
|
|
507
|
+
const retry = await p.confirm({
|
|
508
|
+
message: t().selectAnother,
|
|
509
|
+
initialValue: true,
|
|
510
|
+
});
|
|
511
|
+
if (p.isCancel(retry))
|
|
512
|
+
return cancel();
|
|
513
|
+
if (retry)
|
|
514
|
+
return back();
|
|
515
|
+
// User chose not to retry
|
|
516
|
+
return cancel();
|
|
517
|
+
}
|
|
518
|
+
return next({
|
|
519
|
+
project: selectedProject,
|
|
520
|
+
shopSlug: selectedProject.shop.slug,
|
|
521
|
+
});
|
|
522
|
+
},
|
|
523
|
+
};
|
|
524
|
+
/**
|
|
525
|
+
* Step 4: Code source selection
|
|
526
|
+
*
|
|
527
|
+
* This step handles both the source type selection AND template selection
|
|
528
|
+
* in a single wizard step. Uses internal loop for proper back navigation:
|
|
529
|
+
* - Back from template list → returns to "Empty/Template" prompt (same step)
|
|
530
|
+
* - Back from "Empty/Template" → returns to previous wizard step (Project)
|
|
531
|
+
*/
|
|
532
|
+
const stepCodeSource = {
|
|
533
|
+
id: "codeSource",
|
|
534
|
+
title: () => t().codeSource,
|
|
535
|
+
stateKeys: ["codeSource"],
|
|
536
|
+
canGoBack: true,
|
|
537
|
+
async execute(state, ctx) {
|
|
538
|
+
// Loop to handle internal back navigation within this step
|
|
539
|
+
while (true) {
|
|
540
|
+
// First prompt: choose source type
|
|
541
|
+
const choice = await selectWithBack({
|
|
542
|
+
message: t().codeSource,
|
|
543
|
+
options: [
|
|
544
|
+
{ value: "empty", label: t().emptyProject },
|
|
545
|
+
{ value: "template", label: t().templateRegistry },
|
|
546
|
+
],
|
|
547
|
+
canGoBack: ctx.canGoBack,
|
|
548
|
+
});
|
|
549
|
+
if (isCancel(choice))
|
|
550
|
+
return cancel();
|
|
551
|
+
if (isBack(choice))
|
|
552
|
+
return back(); // Goes to previous wizard step (Project)
|
|
553
|
+
if (choice === "empty") {
|
|
554
|
+
return next({ codeSource: { type: "empty" } });
|
|
555
|
+
}
|
|
556
|
+
// User chose template - fetch and show template list
|
|
557
|
+
const spinner = ora(t().fetchingTemplates).start();
|
|
558
|
+
const templates = await fetchRemoteTemplates(state.__apiUrl, state.__token);
|
|
559
|
+
spinner.stop();
|
|
560
|
+
if (!templates || templates.length === 0) {
|
|
561
|
+
p.log.warn(t().noTemplates);
|
|
562
|
+
return next({ codeSource: { type: "empty" } });
|
|
563
|
+
}
|
|
564
|
+
// Clear screen for clean template selection UI
|
|
565
|
+
clearScreen();
|
|
566
|
+
p.intro(pc.bgCyan(pc.black(` ${t().wizardTitle} `)));
|
|
567
|
+
// Show progress - template path adds +1 step (5/6 instead of 4/5)
|
|
568
|
+
const templateCurrent = ctx.stepIndex + 2; // 5
|
|
569
|
+
const templateTotal = ctx.totalSteps + 1; // 6
|
|
570
|
+
const filledTemplate = pc.green('●'.repeat(templateCurrent));
|
|
571
|
+
const emptyTemplate = pc.dim('○'.repeat(templateTotal - templateCurrent));
|
|
572
|
+
p.log.step(pc.cyan(`${t().stepOf(templateCurrent, templateTotal)}: ${t().selectTemplate}`));
|
|
573
|
+
p.log.message(`${filledTemplate}${emptyTemplate}`, { symbol: '' });
|
|
574
|
+
// Second prompt: select specific template
|
|
575
|
+
const templateId = await selectWithBack({
|
|
576
|
+
message: t().selectTemplate,
|
|
577
|
+
options: templates.map((tpl) => ({
|
|
578
|
+
value: tpl.id,
|
|
579
|
+
label: `${tpl.name}${tpl.uiLibrary ? ` (${tpl.uiLibrary})` : ""}`,
|
|
580
|
+
hint: tpl.description || undefined,
|
|
581
|
+
})),
|
|
582
|
+
canGoBack: true,
|
|
583
|
+
});
|
|
584
|
+
if (isCancel(templateId))
|
|
585
|
+
return cancel();
|
|
586
|
+
if (isBack(templateId)) {
|
|
587
|
+
// Back from template list → loop back to first prompt (Empty/Template)
|
|
588
|
+
// Clear screen and redraw the step header before looping
|
|
589
|
+
// Show 4/5 (default total) since user hasn't chosen path yet
|
|
590
|
+
const backCurrent = ctx.stepIndex + 1; // 4
|
|
591
|
+
const backTotal = ctx.totalSteps; // 5 (base)
|
|
592
|
+
const filledBack = pc.green('●'.repeat(backCurrent));
|
|
593
|
+
const emptyBack = pc.dim('○'.repeat(backTotal - backCurrent));
|
|
594
|
+
clearScreen();
|
|
595
|
+
p.intro(pc.bgCyan(pc.black(` ${t().wizardTitle} `)));
|
|
596
|
+
p.log.step(pc.cyan(`${t().stepOf(backCurrent, backTotal)}: ${t().codeSource}`));
|
|
597
|
+
p.log.message(`${filledBack}${emptyBack}`, { symbol: '' });
|
|
598
|
+
continue; // Go back to start of loop (first prompt)
|
|
599
|
+
}
|
|
600
|
+
const selectedTemplate = templates.find((tpl) => tpl.id === templateId);
|
|
601
|
+
if (!selectedTemplate?.repoUrl) {
|
|
602
|
+
p.log.warn(t().templateNoRepo);
|
|
603
|
+
return next({ codeSource: { type: "empty" } });
|
|
604
|
+
}
|
|
605
|
+
return next({
|
|
606
|
+
codeSource: {
|
|
607
|
+
type: "template",
|
|
608
|
+
templateRepoUrl: selectedTemplate.repoUrl,
|
|
609
|
+
templateName: selectedTemplate.name,
|
|
610
|
+
},
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
},
|
|
614
|
+
};
|
|
615
|
+
/**
|
|
616
|
+
* Step 5 (or 6 for template path): Confirmation and dependencies
|
|
617
|
+
*/
|
|
618
|
+
const stepConfirm = {
|
|
619
|
+
id: "confirm",
|
|
620
|
+
title: () => t().summary,
|
|
621
|
+
stateKeys: ["shouldInstall"],
|
|
622
|
+
canGoBack: true,
|
|
623
|
+
// Dynamic progress: 5/5 for empty path, 6/6 for template path
|
|
624
|
+
getProgress(state, defaultProgress) {
|
|
625
|
+
if (state.codeSource?.type === "template") {
|
|
626
|
+
return {
|
|
627
|
+
current: defaultProgress.current + 1, // 6
|
|
628
|
+
total: defaultProgress.total + 1, // 6
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
return defaultProgress; // 5/5 for empty
|
|
632
|
+
},
|
|
633
|
+
async execute(state, ctx) {
|
|
634
|
+
// Display summary with consistent alignment
|
|
635
|
+
const templateName = state.codeSource.type === "empty"
|
|
636
|
+
? "Minimal"
|
|
637
|
+
: state.codeSource.templateName || "Template";
|
|
638
|
+
const summaryLines = [
|
|
639
|
+
` ${pc.dim("Directory:")} ${state.targetDir}`,
|
|
640
|
+
` ${pc.dim("Team:")} ${state.team.name}`,
|
|
641
|
+
` ${pc.dim("Project:")} ${state.project.name}`,
|
|
642
|
+
` ${pc.dim("Shop:")} ${state.shopSlug}`,
|
|
643
|
+
` ${pc.dim("Template:")} ${templateName}`,
|
|
644
|
+
];
|
|
645
|
+
console.log(); // Empty line before summary
|
|
646
|
+
console.log(pc.bold(t().summary + ":"));
|
|
647
|
+
summaryLines.forEach(line => console.log(line));
|
|
648
|
+
console.log(); // Empty line after summary
|
|
649
|
+
const confirmChoice = await selectWithBack({
|
|
650
|
+
message: t().continue,
|
|
651
|
+
options: [
|
|
652
|
+
{ value: "yes", label: t().yes },
|
|
653
|
+
{ value: "no", label: t().cancel },
|
|
654
|
+
],
|
|
655
|
+
canGoBack: ctx.canGoBack,
|
|
656
|
+
});
|
|
657
|
+
if (isCancel(confirmChoice))
|
|
658
|
+
return cancel();
|
|
659
|
+
if (isBack(confirmChoice))
|
|
660
|
+
return back();
|
|
661
|
+
if (confirmChoice === "no") {
|
|
662
|
+
p.cancel(t().cancelled);
|
|
663
|
+
process.exit(0);
|
|
664
|
+
}
|
|
665
|
+
// Ask about installing dependencies
|
|
666
|
+
const installChoice = await p.select({
|
|
667
|
+
message: t().installDeps,
|
|
668
|
+
options: [
|
|
669
|
+
{ value: "yes", label: t().yesInstall(state.__pmInfo.name) },
|
|
670
|
+
{ value: "no", label: t().noInstallLater },
|
|
671
|
+
],
|
|
672
|
+
});
|
|
673
|
+
if (p.isCancel(installChoice))
|
|
674
|
+
return cancel();
|
|
675
|
+
return next({ shouldInstall: installChoice === "yes" });
|
|
676
|
+
},
|
|
677
|
+
};
|
|
678
|
+
// ============================================================================
|
|
679
|
+
// Template Creation Flow (for SaaS developers)
|
|
680
|
+
// ============================================================================
|
|
681
|
+
/**
|
|
682
|
+
* Create a new template project for the registry (SaaS developer flow).
|
|
683
|
+
*/
|
|
684
|
+
async function initTemplateProject(options) {
|
|
685
|
+
p.intro(pc.bgCyan(pc.black(" DoSwiftly CLI - Create Template ")));
|
|
686
|
+
const { getAuthStateSilent } = await import("./auth.js");
|
|
687
|
+
const { isSaasDeveloper } = getAuthStateSilent();
|
|
688
|
+
if (!isSaasDeveloper) {
|
|
689
|
+
p.log.warn("Warning: You don't appear to be a SaaS developer.");
|
|
690
|
+
p.log.info(pc.dim("Template creation works locally, but you'll need the SaaS developer role to register."));
|
|
691
|
+
}
|
|
692
|
+
// Template name
|
|
693
|
+
const templateName = options.name ||
|
|
694
|
+
(await p.text({
|
|
695
|
+
message: "Template name:",
|
|
696
|
+
validate: (v) => {
|
|
697
|
+
if (!v)
|
|
698
|
+
return "Name is required";
|
|
699
|
+
if (!/^[a-z0-9-]+$/.test(v))
|
|
700
|
+
return "Use lowercase letters, numbers, and hyphens only";
|
|
701
|
+
if (v.length < 3)
|
|
702
|
+
return "At least 3 characters";
|
|
703
|
+
return undefined;
|
|
704
|
+
},
|
|
705
|
+
}));
|
|
706
|
+
if (p.isCancel(templateName)) {
|
|
707
|
+
p.cancel(t().cancelled);
|
|
708
|
+
process.exit(0);
|
|
709
|
+
}
|
|
710
|
+
// Template description
|
|
711
|
+
const description = await p.text({
|
|
712
|
+
message: "Template description (optional):",
|
|
713
|
+
placeholder: `DoSwiftly storefront template: ${templateName}`,
|
|
714
|
+
});
|
|
715
|
+
if (p.isCancel(description)) {
|
|
716
|
+
p.cancel(t().cancelled);
|
|
717
|
+
process.exit(0);
|
|
718
|
+
}
|
|
719
|
+
// Base template selection
|
|
720
|
+
const baseTemplate = await p.select({
|
|
721
|
+
message: "Base template to start from:",
|
|
722
|
+
options: [
|
|
723
|
+
{
|
|
724
|
+
value: "storefront-nextjs-shadcn",
|
|
725
|
+
label: "shadcn/ui (Recommended)",
|
|
726
|
+
hint: "Next.js + shadcn/ui + Tailwind CSS",
|
|
727
|
+
},
|
|
728
|
+
{
|
|
729
|
+
value: "storefront-minimal",
|
|
730
|
+
label: "Minimal (Next.js only)",
|
|
731
|
+
hint: "Minimal Next.js starter",
|
|
732
|
+
},
|
|
733
|
+
],
|
|
734
|
+
});
|
|
735
|
+
if (p.isCancel(baseTemplate)) {
|
|
736
|
+
p.cancel(t().cancelled);
|
|
737
|
+
process.exit(0);
|
|
738
|
+
}
|
|
739
|
+
// UI Library label
|
|
740
|
+
const uiLibraryMap = {
|
|
741
|
+
"storefront-nextjs-shadcn": "shadcn/ui",
|
|
742
|
+
"storefront-minimal": "none",
|
|
743
|
+
};
|
|
744
|
+
const uiLibrary = uiLibraryMap[baseTemplate] || "none";
|
|
745
|
+
// Create directory
|
|
746
|
+
const targetDir = templateName;
|
|
747
|
+
if (existsSync(targetDir)) {
|
|
748
|
+
p.log.error(`Directory "${targetDir}" already exists.`);
|
|
749
|
+
process.exit(1);
|
|
750
|
+
}
|
|
751
|
+
const spinner = ora("Creating template project...").start();
|
|
752
|
+
try {
|
|
753
|
+
const templateDir = join(__dirname, "..", "..", "templates", baseTemplate);
|
|
754
|
+
if (!existsSync(templateDir)) {
|
|
755
|
+
throw new Error(`Base template not found: ${baseTemplate}`);
|
|
756
|
+
}
|
|
757
|
+
// Copy template files (with generic placeholders)
|
|
758
|
+
const replacements = {
|
|
759
|
+
"\\{\\{PROJECT_NAME\\}\\}": templateName,
|
|
760
|
+
"\\{\\{SHOP_SLUG\\}\\}": "{{SHOP_SLUG}}",
|
|
761
|
+
"\\{\\{API_URL\\}\\}": "{{API_URL}}",
|
|
762
|
+
"\\{\\{SDK_VERSION\\}\\}": "^1.0.0",
|
|
763
|
+
"\\{\\{COMMERCE_SDK_VERSION\\}\\}": "^1.0.0",
|
|
764
|
+
"\\{\\{STOREFRONT_OPS_VERSION\\}\\}": "^1.0.0",
|
|
765
|
+
};
|
|
766
|
+
copyTemplateDir(templateDir, targetDir, replacements);
|
|
767
|
+
// Create doswiftly-template.json manifest
|
|
768
|
+
const manifest = {
|
|
769
|
+
name: templateName,
|
|
770
|
+
description: description || `DoSwiftly storefront template: ${templateName}`,
|
|
771
|
+
uiLibrary,
|
|
772
|
+
features: [],
|
|
773
|
+
version: "0.1.0",
|
|
774
|
+
};
|
|
775
|
+
writeFileSync(join(targetDir, "doswiftly-template.json"), JSON.stringify(manifest, null, 2) + "\n");
|
|
776
|
+
// Initialize git
|
|
777
|
+
spinner.text = "Initializing git repository...";
|
|
778
|
+
try {
|
|
779
|
+
execSync("git init", { stdio: "pipe", cwd: targetDir });
|
|
780
|
+
execSync("git add -A", { stdio: "pipe", cwd: targetDir });
|
|
781
|
+
execSync(`git commit -m "chore: initialize ${templateName} template"`, {
|
|
782
|
+
stdio: "pipe",
|
|
783
|
+
cwd: targetDir,
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
catch {
|
|
787
|
+
// Non-fatal
|
|
788
|
+
}
|
|
789
|
+
spinner.succeed(pc.green("Template project created!"));
|
|
790
|
+
p.outro(pc.green("Success!"));
|
|
791
|
+
p.log.info(pc.dim(`Created template project in: ${pc.cyan(targetDir)}/`));
|
|
792
|
+
p.log.message("");
|
|
793
|
+
p.log.message(pc.bold("Next steps:"));
|
|
794
|
+
p.log.message(pc.cyan(` cd ${templateName}`));
|
|
795
|
+
p.log.message(pc.dim(" # Customize the template to your needs"));
|
|
796
|
+
p.log.message(pc.cyan(" git remote add origin <your-github-repo-url>"));
|
|
797
|
+
p.log.message(pc.cyan(" git push -u origin main"));
|
|
798
|
+
p.log.message(pc.cyan(" doswiftly template register"));
|
|
799
|
+
p.log.message("");
|
|
800
|
+
}
|
|
801
|
+
catch (error) {
|
|
802
|
+
spinner.fail(pc.red("Failed to create template project"));
|
|
803
|
+
console.error(pc.red(error.message));
|
|
804
|
+
process.exit(1);
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
// ============================================================================
|
|
808
|
+
// Project Creation
|
|
809
|
+
// ============================================================================
|
|
810
|
+
/**
|
|
811
|
+
* Execute project creation after wizard completes
|
|
812
|
+
*/
|
|
813
|
+
async function createProject(state) {
|
|
814
|
+
const { targetDir, projectName, shopSlug, codeSource, shouldInstall, __apiUrl: apiUrl, __pmInfo: pmInfo, } = state;
|
|
815
|
+
// Clear screen for clean project creation output
|
|
816
|
+
clearScreen();
|
|
817
|
+
p.intro(pc.bgCyan(pc.black(` ${t().wizardTitle} `)));
|
|
818
|
+
const createSpinner = ora(t().creatingProject).start();
|
|
819
|
+
try {
|
|
820
|
+
// Create target directory if it doesn't exist
|
|
821
|
+
if (!existsSync(targetDir)) {
|
|
822
|
+
mkdirSync(targetDir, { recursive: true });
|
|
823
|
+
}
|
|
824
|
+
// Define placeholder replacements
|
|
825
|
+
const replacements = {
|
|
826
|
+
"\\{\\{PROJECT_NAME\\}\\}": projectName,
|
|
827
|
+
"\\{\\{SHOP_SLUG\\}\\}": shopSlug,
|
|
828
|
+
"\\{\\{API_URL\\}\\}": apiUrl,
|
|
829
|
+
"\\{\\{SDK_VERSION\\}\\}": "^1.0.0",
|
|
830
|
+
"\\{\\{COMMERCE_SDK_VERSION\\}\\}": "^1.0.0",
|
|
831
|
+
"\\{\\{STOREFRONT_OPS_VERSION\\}\\}": "^1.0.0",
|
|
832
|
+
};
|
|
833
|
+
let templateName = "storefront-minimal";
|
|
834
|
+
if (codeSource.type === "template" && codeSource.templateRepoUrl) {
|
|
835
|
+
// Download remote template
|
|
836
|
+
createSpinner.text = t().downloadingTemplate;
|
|
837
|
+
await downloadTemplate(codeSource.templateRepoUrl, targetDir);
|
|
838
|
+
createSpinner.text = t().configuringProject;
|
|
839
|
+
applyReplacementsToDirectory(targetDir, replacements);
|
|
840
|
+
templateName = codeSource.templateName || "remote-template";
|
|
841
|
+
}
|
|
842
|
+
else {
|
|
843
|
+
// Copy minimal template
|
|
844
|
+
createSpinner.text = t().copyingTemplate;
|
|
845
|
+
const minimalTemplate = join(__dirname, "..", "..", "templates", "storefront-minimal");
|
|
846
|
+
if (!existsSync(minimalTemplate)) {
|
|
847
|
+
throw new Error("Minimal template not found. Please reinstall @doswiftly/cli.");
|
|
848
|
+
}
|
|
849
|
+
copyTemplateDir(minimalTemplate, targetDir, replacements);
|
|
850
|
+
}
|
|
851
|
+
// Create .env.local
|
|
852
|
+
createSpinner.text = t().creatingEnvLocal;
|
|
853
|
+
createEnvLocal(targetDir, shopSlug, apiUrl, projectName);
|
|
854
|
+
// Create doswiftly.config.ts
|
|
855
|
+
createSpinner.text = t().creatingConfig;
|
|
856
|
+
createDoswiftlyConfig(targetDir, shopSlug, apiUrl, projectName);
|
|
857
|
+
// Create .doswiftly directory for cache
|
|
858
|
+
mkdirSync(join(targetDir, ".doswiftly"), { recursive: true });
|
|
859
|
+
writeFileSync(join(targetDir, ".doswiftly", ".gitignore"), "*\n");
|
|
860
|
+
// Create .gitignore if it doesn't exist (npm strips .gitignore from packages)
|
|
861
|
+
const gitignorePath = join(targetDir, ".gitignore");
|
|
862
|
+
if (!existsSync(gitignorePath)) {
|
|
863
|
+
const gitignoreContent = `# Dependencies
|
|
864
|
+
node_modules/
|
|
865
|
+
.pnpm-store/
|
|
866
|
+
|
|
867
|
+
# Next.js
|
|
868
|
+
.next/
|
|
869
|
+
out/
|
|
870
|
+
|
|
871
|
+
# Build
|
|
872
|
+
dist/
|
|
873
|
+
build/
|
|
874
|
+
|
|
875
|
+
# Environment files
|
|
876
|
+
.env
|
|
877
|
+
.env.local
|
|
878
|
+
.env.*.local
|
|
879
|
+
|
|
880
|
+
# Debug logs
|
|
881
|
+
npm-debug.log*
|
|
882
|
+
yarn-debug.log*
|
|
883
|
+
yarn-error.log*
|
|
884
|
+
pnpm-debug.log*
|
|
885
|
+
|
|
886
|
+
# IDE
|
|
887
|
+
.idea/
|
|
888
|
+
.vscode/
|
|
889
|
+
*.swp
|
|
890
|
+
*.swo
|
|
891
|
+
|
|
892
|
+
# OS
|
|
893
|
+
.DS_Store
|
|
894
|
+
Thumbs.db
|
|
895
|
+
|
|
896
|
+
# DoSwiftly cache
|
|
897
|
+
.doswiftly/
|
|
898
|
+
|
|
899
|
+
# TypeScript
|
|
900
|
+
*.tsbuildinfo
|
|
901
|
+
|
|
902
|
+
# Testing
|
|
903
|
+
coverage/
|
|
904
|
+
`;
|
|
905
|
+
writeFileSync(gitignorePath, gitignoreContent);
|
|
906
|
+
}
|
|
907
|
+
// Auto-create a default profile if none exists
|
|
908
|
+
if (!hasProfiles()) {
|
|
909
|
+
try {
|
|
910
|
+
addProfile({
|
|
911
|
+
name: "default",
|
|
912
|
+
apiUrl: apiUrl,
|
|
913
|
+
description: `Auto-created by doswiftly init for ${projectName}`,
|
|
914
|
+
});
|
|
915
|
+
}
|
|
916
|
+
catch {
|
|
917
|
+
// Non-fatal
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
createSpinner.succeed(t().projectCreated);
|
|
921
|
+
// Install dependencies if requested
|
|
922
|
+
if (shouldInstall) {
|
|
923
|
+
const installSpinner = ora(t().installingDeps).start();
|
|
924
|
+
try {
|
|
925
|
+
await installDependencies(pmInfo.name, [], {
|
|
926
|
+
stdio: "pipe",
|
|
927
|
+
cwd: targetDir,
|
|
928
|
+
});
|
|
929
|
+
installSpinner.succeed(t().depsInstalled);
|
|
930
|
+
}
|
|
931
|
+
catch (installError) {
|
|
932
|
+
installSpinner.fail(t().installFailed);
|
|
933
|
+
const errorMessage = installError instanceof Error
|
|
934
|
+
? installError.message
|
|
935
|
+
: String(installError);
|
|
936
|
+
if (errorMessage) {
|
|
937
|
+
p.log.info(pc.dim(errorMessage.split("\n")[0]));
|
|
938
|
+
}
|
|
939
|
+
p.log.warn(t().runManually(projectName, pmInfo.name));
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
// Initialize git repository
|
|
943
|
+
await initGitRepository(targetDir, templateName);
|
|
944
|
+
// Success message
|
|
945
|
+
p.log.message("");
|
|
946
|
+
p.log.success(pc.green(t().projectReady));
|
|
947
|
+
p.log.message("");
|
|
948
|
+
p.log.message(pc.dim(t().nextSteps));
|
|
949
|
+
if (targetDir !== process.cwd()) {
|
|
950
|
+
p.log.message(pc.cyan(` cd ${projectName}`));
|
|
951
|
+
}
|
|
952
|
+
if (!shouldInstall) {
|
|
953
|
+
p.log.message(pc.cyan(` ${pmInfo.name} install`));
|
|
954
|
+
}
|
|
955
|
+
p.log.message(pc.cyan(" doswiftly dev"));
|
|
956
|
+
p.log.message("");
|
|
957
|
+
p.log.message(pc.dim(t().documentation));
|
|
958
|
+
p.log.message("");
|
|
959
|
+
}
|
|
960
|
+
catch (error) {
|
|
961
|
+
createSpinner.fail(pc.red(t().failedCreateProject));
|
|
962
|
+
console.error(pc.red(error.message));
|
|
963
|
+
process.exit(1);
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
// ============================================================================
|
|
967
|
+
// Main Init Command
|
|
968
|
+
// ============================================================================
|
|
969
|
+
export async function initCommand(options = {}) {
|
|
970
|
+
// Branch to template creation flow if --create-template is specified
|
|
971
|
+
if (options.createTemplate) {
|
|
972
|
+
return initTemplateProject(options);
|
|
973
|
+
}
|
|
974
|
+
// Check authentication first
|
|
975
|
+
const token = getStoredToken();
|
|
976
|
+
if (!token) {
|
|
977
|
+
p.intro(pc.bgRed(pc.white(" Error ")));
|
|
978
|
+
p.log.error(t().notLoggedIn);
|
|
979
|
+
p.log.info(pc.cyan(t().runAuthLogin));
|
|
980
|
+
process.exit(1);
|
|
981
|
+
}
|
|
982
|
+
const apiUrl = getApiUrl();
|
|
983
|
+
// Fetch data upfront
|
|
984
|
+
const spinner = ora(t().fetchingData).start();
|
|
985
|
+
const teams = await fetchTeams(apiUrl, token);
|
|
986
|
+
const projects = await fetchProjects(apiUrl, token);
|
|
987
|
+
spinner.stop();
|
|
988
|
+
// Check teams exist
|
|
989
|
+
if (teams.length === 0) {
|
|
990
|
+
p.intro(pc.bgRed(pc.white(" Error ")));
|
|
991
|
+
p.log.error(t().noTeams);
|
|
992
|
+
p.log.info(pc.cyan(t().createTeamHint));
|
|
993
|
+
process.exit(1);
|
|
994
|
+
}
|
|
995
|
+
// Detect package manager
|
|
996
|
+
const pmInfo = options.pm
|
|
997
|
+
? await getPackageManagerInfo(options.pm)
|
|
998
|
+
: await detectPackageManager();
|
|
999
|
+
// Define wizard steps
|
|
1000
|
+
const steps = [
|
|
1001
|
+
stepDirectory,
|
|
1002
|
+
stepTeam,
|
|
1003
|
+
stepProject,
|
|
1004
|
+
stepCodeSource,
|
|
1005
|
+
stepConfirm,
|
|
1006
|
+
];
|
|
1007
|
+
// Initial state with internal data
|
|
1008
|
+
const initialState = {
|
|
1009
|
+
__teams: teams,
|
|
1010
|
+
__projects: projects,
|
|
1011
|
+
__apiUrl: apiUrl,
|
|
1012
|
+
__token: token,
|
|
1013
|
+
__pmInfo: pmInfo,
|
|
1014
|
+
};
|
|
1015
|
+
// Run wizard
|
|
1016
|
+
const engine = new WizardEngine(steps, initialState, {
|
|
1017
|
+
title: t().wizardTitle,
|
|
1018
|
+
cancelMessage: t().cancelled,
|
|
1019
|
+
});
|
|
1020
|
+
const result = await engine.run();
|
|
1021
|
+
if (!result) {
|
|
1022
|
+
// Wizard was cancelled
|
|
1023
|
+
process.exit(0);
|
|
1024
|
+
}
|
|
1025
|
+
// Execute project creation with final state
|
|
1026
|
+
await createProject(result);
|
|
1027
|
+
}
|
|
1028
|
+
//# sourceMappingURL=init.js.map
|