create-fullstack-scaffold 0.4.21 → 0.4.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -204,11 +204,13 @@ function getExcludePatterns(resolved, allManifests) {
204
204
  if (!resolved.modules.has("tenant")) {
205
205
  excludes.push("src/tenant");
206
206
  excludes.push("tenant.html");
207
- excludes.push("src/merchant");
208
- excludes.push("merchant.html");
209
207
  excludes.push("src/server/middleware/tenant-isolation.ts");
210
208
  excludes.push("src/server/middleware/__tests__/tenant-isolation.test.ts");
211
209
  }
210
+ if (!resolved.modules.has("merchant")) {
211
+ excludes.push("src/merchant");
212
+ excludes.push("merchant.html");
213
+ }
212
214
  if (!resolved.hasClient) {
213
215
  excludes.push("src/client");
214
216
  excludes.push("index.html");
@@ -229,15 +231,16 @@ function getExcludePatterns(resolved, allManifests) {
229
231
  }
230
232
  excludes.push("src/client/preset-ui-config.ts");
231
233
  const standaloneSharedModules = {
232
- cart: ["CartPage"],
233
- community: ["TopicsPage"],
234
- dashboard: ["DashboardPage"]
234
+ cart: { pages: ["CartPage"] },
235
+ community: { pages: ["TopicsPage", "ProfilePage"], serverModules: ["content"] },
236
+ dashboard: { pages: ["DashboardPage"] }
235
237
  };
236
- for (const [moduleName, requiredPages] of Object.entries(standaloneSharedModules)) {
237
- const hasRelevantPage = [...resolved.modules.values()].some(
238
- (m) => m.clientPages?.some((p) => requiredPages.includes(p.name)) ?? false
238
+ for (const [moduleName, config] of Object.entries(standaloneSharedModules)) {
239
+ const hasRelevantPage = config.pages && [...resolved.modules.values()].some(
240
+ (m) => m.clientPages?.some((p) => config.pages.includes(p.name)) ?? false
239
241
  );
240
- if (!hasRelevantPage) {
242
+ const hasRelevantServerModule = config.serverModules?.some((sm) => resolved.modules.has(sm)) ?? false;
243
+ if (!hasRelevantPage && !hasRelevantServerModule) {
241
244
  excludes.push(`src/shared/modules/${moduleName}`);
242
245
  }
243
246
  }
@@ -261,6 +264,10 @@ function getExcludePatterns(resolved, allManifests) {
261
264
  }
262
265
  if (!resolved.modules.has("captcha")) {
263
266
  excludes.push("src/server/utils/__tests__/captcha.test.ts");
267
+ excludes.push("src/admin/components/CaptchaModal.tsx");
268
+ excludes.push("src/admin/stores/captchaStore.ts");
269
+ excludes.push("src/admin/stores/__tests__/captchaStore.test.ts");
270
+ excludes.push("src/admin/stores/__tests__/captchaStoreBranches.test.ts");
264
271
  }
265
272
  return excludes;
266
273
  }
@@ -288,6 +295,8 @@ function getGeneratedFiles(resolved) {
288
295
  }
289
296
  if (resolved.hasClient && resolved.modules.has("admin")) {
290
297
  files.push("src/admin/App.tsx");
298
+ files.push("src/admin/components/index.ts");
299
+ files.push("src/admin/services/apiClient.ts");
291
300
  }
292
301
  if (resolved.hasClient && !resolved.modules.has("admin")) {
293
302
  files.push("vite.config.ts");
@@ -727,6 +736,93 @@ ${protectedRouteElements.join("\n")}
727
736
  }
728
737
  `;
729
738
  }
739
+ function generateAdminComponentsIndex(resolved) {
740
+ if (!resolved.hasAdmin) return null;
741
+ const lines = [
742
+ `export { UserTable } from './UserTable'`,
743
+ `export { StatsCard } from './StatsCard'`,
744
+ `export { PageHeader } from './PageHeader'`,
745
+ `export { UserFormModal } from './UserFormModal'`,
746
+ `export { ProtectedRoute } from './ProtectedRoute'`
747
+ ];
748
+ if (resolved.hasCaptcha) {
749
+ lines.push(`export { CaptchaModal } from './CaptchaModal'`);
750
+ }
751
+ lines.push(`export { PermissionGuard, PermissionButton, Can, Cannot } from './PermissionGuard'`);
752
+ return lines.join("\n") + "\n";
753
+ }
754
+ function generateAdminApiClient(resolved) {
755
+ if (!resolved.hasAdmin) return null;
756
+ const captchaImport = resolved.hasCaptcha ? `import { useCaptchaStore } from '../stores/captchaStore'
757
+ ` : "";
758
+ const captchaFetchSetup = resolved.hasCaptcha ? ` const showCaptcha = useCaptchaStore.getState().show
759
+
760
+ ` : "";
761
+ const captchaHandler = resolved.hasCaptcha ? ` onShowCaptcha: async config => {
762
+ return showCaptcha({
763
+ type: config.type,
764
+ captchaUrl: config.captchaUrl,
765
+ })
766
+ },` : ` onShowCaptcha: async () => true,`;
767
+ return `/**
768
+ * @framework-baseline ab16e97716a7556e
769
+ * @framework-modify
770
+ * @reason Conditional captcha support based on preset modules
771
+ * @impact Captcha module excluded = no captchaStore import
772
+ */
773
+
774
+ import { hc } from 'hono/client'
775
+ import { WSClientImpl } from '@shared/core/ws-client'
776
+ import { SSEClientImpl } from '@shared/core/sse-client'
777
+ import { createRequestInterceptor } from './requestInterceptor'
778
+ ${captchaImport}import type { AdminApiType } from '@server/index'
779
+
780
+ const baseUrl = import.meta.env.API_BASE_URL || window.location.origin
781
+
782
+ const TOKEN_KEY = 'admin-storage'
783
+
784
+ function clearAuthAndRedirect(): void {
785
+ localStorage.removeItem(TOKEN_KEY)
786
+ if (window.location.pathname !== '/admin/login') {
787
+ window.location.href = '/admin/login'
788
+ }
789
+ }
790
+
791
+ function getAuthToken(): string | null {
792
+ try {
793
+ const stored = localStorage.getItem(TOKEN_KEY)
794
+ if (stored) {
795
+ const parsed = JSON.parse(stored)
796
+ return parsed.state?.token || null
797
+ }
798
+ } catch {
799
+ return null
800
+ }
801
+ return null
802
+ }
803
+
804
+ function createCustomFetch() {
805
+ ${captchaFetchSetup} return createRequestInterceptor({
806
+ onShowLogin: clearAuthAndRedirect,
807
+ ${captchaHandler} })
808
+ }
809
+
810
+ export const apiClient = hc<AdminApiType>(baseUrl, {
811
+ fetch: createCustomFetch() as typeof fetch,
812
+ webSocket: url => new WSClientImpl(url) as unknown as WebSocket,
813
+ sse: url => {
814
+ const token = getAuthToken()
815
+ const headers: Record<string, string> = {}
816
+ if (token) {
817
+ headers['Authorization'] = \`Bearer \${token}\`
818
+ }
819
+ return new SSEClientImpl(url, headers)
820
+ },
821
+ })
822
+
823
+ export { api } from '@shared/core/api-request'
824
+ `;
825
+ }
730
826
 
731
827
  // src/generators/db-schema-barrel.ts
732
828
  function generateDbSchemaBarrel(resolved) {
@@ -1686,9 +1782,9 @@ function generateSharedModulesIndex(resolved) {
1686
1782
  "captcha"
1687
1783
  ];
1688
1784
  const STANDALONE_SHARED_MODULES2 = {
1689
- cart: ["CartPage.tsx"],
1690
- community: ["TopicsPage.tsx"],
1691
- dashboard: ["DashboardPage.tsx"]
1785
+ cart: { pages: ["CartPage.tsx"] },
1786
+ community: { pages: ["TopicsPage.tsx", "ProfilePage.tsx"], serverModules: ["content"] },
1787
+ dashboard: { pages: ["DashboardPage.tsx"] }
1692
1788
  };
1693
1789
  for (const moduleName of moduleOrder2) {
1694
1790
  if (!resolved.modules.has(moduleName)) continue;
@@ -1716,11 +1812,12 @@ function generateSharedModulesIndex(resolved) {
1716
1812
  }
1717
1813
  }
1718
1814
  }
1719
- for (const [moduleName, requiredPages] of Object.entries(STANDALONE_SHARED_MODULES2)) {
1720
- const hasRelevantPage = [...resolved.modules.values()].some(
1721
- (m) => m.clientPages?.some((p) => requiredPages.includes(p.name + ".tsx")) ?? false
1815
+ for (const [moduleName, config] of Object.entries(STANDALONE_SHARED_MODULES2)) {
1816
+ const hasRelevantPage = config.pages && [...resolved.modules.values()].some(
1817
+ (m) => m.clientPages?.some((p) => config.pages.includes(p.name + ".tsx")) ?? false
1722
1818
  );
1723
- if (!hasRelevantPage) continue;
1819
+ const hasRelevantServerModule = config.serverModules?.some((sm) => resolved.modules.has(sm)) ?? false;
1820
+ if (!hasRelevantPage && !hasRelevantServerModule) continue;
1724
1821
  const exports = MODULE_EXPORTS[moduleName];
1725
1822
  if (!exports) continue;
1726
1823
  lines.push(`export {
@@ -2148,9 +2245,9 @@ var ADDITIONAL_PATHS_MAP = {
2148
2245
  permission: ["role", "audit"]
2149
2246
  };
2150
2247
  var STANDALONE_SHARED_MODULES = {
2151
- cart: ["CartPage.tsx"],
2152
- community: ["TopicsPage.tsx"],
2153
- dashboard: ["DashboardPage.tsx"]
2248
+ cart: { pages: ["CartPage.tsx"] },
2249
+ community: { pages: ["TopicsPage.tsx", "ProfilePage.tsx"], serverModules: ["content"] },
2250
+ dashboard: { pages: ["DashboardPage.tsx"] }
2154
2251
  };
2155
2252
  var moduleOrder = [
2156
2253
  "chat",
@@ -2234,12 +2331,13 @@ export {
2234
2331
  }
2235
2332
  function shouldIncludeModule(moduleName, resolved) {
2236
2333
  if (resolved.modules.has(moduleName)) return true;
2237
- const standalonePages = STANDALONE_SHARED_MODULES[moduleName];
2238
- if (standalonePages) {
2239
- const hasRelevantPage = [...resolved.modules.values()].some(
2240
- (m) => m.clientPages?.some((p) => standalonePages.includes(p.name + ".tsx")) ?? false
2334
+ const standalone = STANDALONE_SHARED_MODULES[moduleName];
2335
+ if (standalone) {
2336
+ const hasRelevantPage = standalone.pages && [...resolved.modules.values()].some(
2337
+ (m) => m.clientPages?.some((p) => standalone.pages.includes(p.name + ".tsx")) ?? false
2241
2338
  );
2242
- return hasRelevantPage;
2339
+ const hasRelevantServerModule = standalone.serverModules?.some((sm) => resolved.modules.has(sm)) ?? false;
2340
+ return !!(hasRelevantPage || hasRelevantServerModule);
2243
2341
  }
2244
2342
  for (const [parentModule, additionalPaths] of Object.entries(ADDITIONAL_PATHS_MAP)) {
2245
2343
  if (additionalPaths.includes(moduleName) && resolved.modules.has(parentModule)) {
@@ -2680,7 +2778,8 @@ function filterPackageJson(pkg, resolved) {
2680
2778
  packagesToRemove.add(pkg2);
2681
2779
  }
2682
2780
  }
2683
- if (!resolved.modules.has("admin")) {
2781
+ const hasAntdConsumer = resolved.modules.has("admin") || resolved.modules.has("tenant") || resolved.modules.has("merchant");
2782
+ if (!hasAntdConsumer) {
2684
2783
  for (const pkg2 of ADMIN_PANEL_PACKAGES) {
2685
2784
  packagesToRemove.add(pkg2);
2686
2785
  }
@@ -2749,8 +2848,12 @@ function generateViteConfig(resolved, templateDir) {
2749
2848
  aliasesToRemove.push("@admin");
2750
2849
  }
2751
2850
  if (!resolved.modules.has("tenant")) {
2752
- entriesToRemove.push("tenant", "merchant");
2753
- aliasesToRemove.push("@tenant", "@merchant");
2851
+ entriesToRemove.push("tenant");
2852
+ aliasesToRemove.push("@tenant");
2853
+ }
2854
+ if (!resolved.modules.has("merchant")) {
2855
+ entriesToRemove.push("merchant");
2856
+ aliasesToRemove.push("@merchant");
2754
2857
  }
2755
2858
  for (const name of entriesToRemove) {
2756
2859
  const re = new RegExp(
@@ -3756,6 +3859,19 @@ async function createProject(projectNameOrOptions, useCurrentDir = false, preset
3756
3859
  await fs.ensureDir(path.join(targetDir, "src/admin"));
3757
3860
  await fs.writeFile(path.join(targetDir, "src/admin/App.tsx"), adminAppContent);
3758
3861
  }
3862
+ const adminComponentsIndex = generateAdminComponentsIndex(resolved);
3863
+ if (adminComponentsIndex) {
3864
+ await fs.ensureDir(path.join(targetDir, "src/admin/components"));
3865
+ await fs.writeFile(
3866
+ path.join(targetDir, "src/admin/components/index.ts"),
3867
+ adminComponentsIndex
3868
+ );
3869
+ }
3870
+ const adminApiClient = generateAdminApiClient(resolved);
3871
+ if (adminApiClient) {
3872
+ await fs.ensureDir(path.join(targetDir, "src/admin/services"));
3873
+ await fs.writeFile(path.join(targetDir, "src/admin/services/apiClient.ts"), adminApiClient);
3874
+ }
3759
3875
  }
3760
3876
  const serverAppContent = generateServerApp(resolved);
3761
3877
  await fs.writeFile(path.join(targetDir, "src/server/app.ts"), serverAppContent);