@screenbook/cli 1.7.0 → 1.7.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/dist/index.mjs CHANGED
@@ -827,23 +827,22 @@ async function generateCoverageData(config, cwd, screens) {
827
827
  cwd,
828
828
  ignore: config.ignore
829
829
  });
830
- new Set(screens.map((s) => {
831
- const parts = s.id.split(".");
832
- return parts.slice(0, -1).join("/") || parts[0];
833
- }));
830
+ const metaDirs = /* @__PURE__ */ new Set();
831
+ for (const screen of screens) if (screen.filePath) {
832
+ const absoluteDir = dirname(screen.filePath);
833
+ const relativeDir = absoluteDir.startsWith(cwd) ? absoluteDir.slice(cwd.length + 1) : absoluteDir;
834
+ metaDirs.add(relativeDir);
835
+ }
834
836
  const missing = [];
835
837
  for (const routeFile of routeFiles) {
836
838
  const routeDir = dirname(routeFile);
837
- if (!screens.some((s) => {
838
- const screenDir = s.id.replace(/\./g, "/");
839
- return routeDir.includes(screenDir) || screenDir.includes(routeDir.replace(/^src\/pages\//, "").replace(/^app\//, ""));
840
- })) missing.push({
839
+ if (!metaDirs.has(routeDir)) missing.push({
841
840
  route: routeFile,
842
841
  suggestedPath: join(dirname(routeFile), "screen.meta.ts")
843
842
  });
844
843
  }
845
844
  const total = routeFiles.length > 0 ? routeFiles.length : screens.length;
846
- const covered = screens.length;
845
+ const covered = routeFiles.length > 0 ? routeFiles.length - missing.length : screens.length;
847
846
  const percentage = total > 0 ? Math.round(covered / total * 100) : 100;
848
847
  const byOwner = {};
849
848
  for (const screen of screens) {
@@ -3326,6 +3325,83 @@ function walkTemplateNodes(nodes, callback) {
3326
3325
 
3327
3326
  //#endregion
3328
3327
  //#region src/commands/generate.ts
3328
+ /**
3329
+ * Common paths where Vue Router configuration files are typically located
3330
+ */
3331
+ const VUE_ROUTER_CONFIG_PATHS = [
3332
+ "src/router/routes.ts",
3333
+ "src/router/index.ts",
3334
+ "src/router.ts",
3335
+ "src/routes.ts",
3336
+ "router/routes.ts",
3337
+ "router/index.ts"
3338
+ ];
3339
+ /**
3340
+ * Default patterns to exclude from screen.meta.ts generation.
3341
+ * These directories typically contain reusable components, not navigable screens.
3342
+ * @see https://github.com/wadakatu/screenbook/issues/170
3343
+ */
3344
+ const DEFAULT_EXCLUDE_PATTERNS = [
3345
+ "**/components/**",
3346
+ "**/shared/**",
3347
+ "**/utils/**",
3348
+ "**/hooks/**",
3349
+ "**/composables/**",
3350
+ "**/stores/**",
3351
+ "**/services/**",
3352
+ "**/helpers/**",
3353
+ "**/lib/**",
3354
+ "**/common/**"
3355
+ ];
3356
+ /**
3357
+ * Check if a path matches any of the exclude patterns
3358
+ */
3359
+ function matchesExcludePattern(filePath, excludePatterns) {
3360
+ for (const pattern of excludePatterns) {
3361
+ const cleanPattern = pattern.replace(/\*\*/g, "").replace(/\*/g, "").replace(/^\//, "").replace(/\/$/, "");
3362
+ if (cleanPattern && filePath.includes(`/${cleanPattern}/`)) return true;
3363
+ if (cleanPattern && filePath.startsWith(`${cleanPattern}/`)) return true;
3364
+ }
3365
+ return false;
3366
+ }
3367
+ /**
3368
+ * Detect Vue Router configuration file in the project
3369
+ */
3370
+ function detectVueRouterConfigFile(cwd) {
3371
+ for (const configPath of VUE_ROUTER_CONFIG_PATHS) {
3372
+ const absolutePath = join(cwd, configPath);
3373
+ if (existsSync(absolutePath)) return absolutePath;
3374
+ }
3375
+ return null;
3376
+ }
3377
+ /**
3378
+ * Build a map from component file paths to their route information.
3379
+ * Normalizes paths for reliable matching.
3380
+ */
3381
+ function buildRouteComponentMap(flatRoutes, cwd) {
3382
+ const map = /* @__PURE__ */ new Map();
3383
+ for (const route of flatRoutes) if (route.componentPath) {
3384
+ const componentDir = dirname(relative(cwd, route.componentPath));
3385
+ const componentNameWithoutExt = basename(route.componentPath).replace(/\.[^.]+$/, "");
3386
+ map.set(componentDir, route);
3387
+ map.set(basename(componentDir), route);
3388
+ map.set(componentNameWithoutExt, route);
3389
+ }
3390
+ return map;
3391
+ }
3392
+ /**
3393
+ * Find matching route from the component map for a given route file
3394
+ */
3395
+ function findMatchingRoute(routeFile, routeComponentMap) {
3396
+ const routeDir = dirname(routeFile);
3397
+ const routeDirName = basename(routeDir);
3398
+ if (routeComponentMap.has(routeDir)) return routeComponentMap.get(routeDir) ?? null;
3399
+ if (routeComponentMap.has(routeDirName)) return routeComponentMap.get(routeDirName) ?? null;
3400
+ for (const [, route] of routeComponentMap) if (route.componentPath) {
3401
+ if (basename(dirname(route.componentPath)) === routeDirName) return route;
3402
+ }
3403
+ return null;
3404
+ }
3329
3405
  const generateCommand = define({
3330
3406
  name: "generate",
3331
3407
  description: "Auto-generate screen.meta.ts files from route files",
@@ -3463,6 +3539,10 @@ async function generateFromRoutesFile(routesFile, cwd, options) {
3463
3539
  for (const route of flatRoutes) {
3464
3540
  const metaPath = determineMetaPath(route, cwd);
3465
3541
  const absoluteMetaPath = resolve(cwd, metaPath);
3542
+ if (matchesExcludePattern(metaPath, DEFAULT_EXCLUDE_PATTERNS)) {
3543
+ skipped++;
3544
+ continue;
3545
+ }
3466
3546
  if (!force && existsSync(absoluteMetaPath)) {
3467
3547
  if (!interactive) {
3468
3548
  skipped++;
@@ -3557,13 +3637,29 @@ async function generateFromRoutesPattern(routesPattern, cwd, options) {
3557
3637
  logger.blank();
3558
3638
  const routeFiles = await glob(routesPattern, {
3559
3639
  cwd,
3560
- ignore
3640
+ ignore: [...ignore, ...DEFAULT_EXCLUDE_PATTERNS]
3561
3641
  });
3562
3642
  if (routeFiles.length === 0) {
3563
3643
  logger.warn(`No route files found matching: ${routesPattern}`);
3564
3644
  return;
3565
3645
  }
3566
3646
  logger.log(`Found ${routeFiles.length} route files`);
3647
+ let routeComponentMap = null;
3648
+ if (routesPattern.includes(".vue")) {
3649
+ const vueRouterConfig = detectVueRouterConfigFile(cwd);
3650
+ if (vueRouterConfig) try {
3651
+ const parseResult = parseVueRouterConfig(vueRouterConfig);
3652
+ const flatRoutes = flattenRoutes(parseResult.routes);
3653
+ if (flatRoutes.length > 0) {
3654
+ routeComponentMap = buildRouteComponentMap(flatRoutes, cwd);
3655
+ logger.log(` ${logger.dim(`(using routes from ${relative(cwd, vueRouterConfig)})`)}`);
3656
+ }
3657
+ for (const warning of parseResult.warnings) logger.warn(warning);
3658
+ } catch (error) {
3659
+ const message = error instanceof Error ? error.message : String(error);
3660
+ logger.warn(`Could not parse Vue Router config: ${message}`);
3661
+ }
3662
+ }
3567
3663
  logger.blank();
3568
3664
  let created = 0;
3569
3665
  let skipped = 0;
@@ -3580,7 +3676,14 @@ async function generateFromRoutesPattern(routesPattern, cwd, options) {
3580
3676
  skipped++;
3581
3677
  continue;
3582
3678
  }
3583
- const screenMeta = inferScreenMeta(routeDir, routesPattern);
3679
+ let screenMeta;
3680
+ const matchedRoute = routeComponentMap ? findMatchingRoute(routeFile, routeComponentMap) : null;
3681
+ if (matchedRoute) screenMeta = {
3682
+ id: matchedRoute.screenId,
3683
+ title: matchedRoute.screenTitle,
3684
+ route: matchedRoute.fullPath
3685
+ };
3686
+ else screenMeta = inferScreenMeta(routeDir, routesPattern);
3584
3687
  let detectedApis = [];
3585
3688
  if (detectApi && apiIntegration) {
3586
3689
  const absoluteRouteFile = join(cwd, routeFile);