Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions services/cli/commands/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import chalk from 'chalk';

import { ComponentScanner } from '../../core/scanner.js';
import { parserFactory } from '../../core/parsers/index.js';
import { ManifestGenerator } from '../../services/manifest-generator.js';
import { ManifestGenerator } from '../../core/manifest-generator.js';

interface GenerateCommandOptions {
output?: string;
Expand Down Expand Up @@ -50,7 +50,7 @@ export async function generateCommand(
const outputPath = options.output || 'library-manifest.json';

// This one line replaces about 25 lines of the old code
generator.generate(components, outputPath, options);
generator.generate(components, outputPath);

spinner.succeed(chalk.green(`✓ Generated manifest: ${outputPath}`));

Expand Down
13 changes: 12 additions & 1 deletion services/cli/commands/serve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { resolve, dirname, join } from 'path';
import chalk from 'chalk';
import { fileURLToPath } from 'url';
import { createServer as createViteServer } from 'vite';
import { createRequire } from 'module';

// ES Module fix for __dirname
const __filename = fileURLToPath(import.meta.url);
Expand Down Expand Up @@ -166,11 +167,16 @@ root.render(<PreviewApp />);
// Wrapped in try/catch so a port collision or misconfiguration never
// prevents the Express dashboard from starting.
try {
const require = createRequire(import.meta.url);//require function scoped to this file to resolve CLI dependencies
const vite = await createViteServer({
root: targetDir,
server: {
middlewareMode: true,
hmr: false,
fs: {
// 2. Give Vite permission to read files from both the user's project AND PatternBook's root
allow: [targetDir, resolve(__dirname, '../../')]
}
},
appType: 'custom',
// Use Vite 8's native OXC transformer for JSX — no plugin needed, no preamble issues.
Expand All @@ -182,6 +188,10 @@ root.render(<PreviewApp />);
include: ['react', 'react-dom', 'react-live', 'prism-react-renderer'],
},
resolve: {
alias: {
'react-live': require.resolve('react-live'),
'prism-react-renderer': require.resolve('prism-react-renderer')
},
dedupe: ['react', 'react-dom'],
},
logLevel: 'warn',
Expand Down Expand Up @@ -246,7 +256,8 @@ root.render(<PreviewApp />);
req.path.startsWith('/patternbook-preview/') ||
req.path.startsWith('/@vite/') ||
req.path.startsWith('/@fs/') ||
req.path.startsWith('/node_modules/')
req.path.startsWith('/node_modules/') ||
req.path.match(/\.[a-zA-Z0-9]+$/)
) {
return next();
}
Expand Down
10 changes: 5 additions & 5 deletions services/core/parsers/react-parser.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Project, SourceFile, FunctionDeclaration, SyntaxKind, VariableDeclaration } from 'ts-morph';
import { Project, SourceFile, FunctionDeclaration, SyntaxKind, VariableDeclaration, Node ,JSDocTag, Identifier ,Symbol,JSDocTagInfo} from 'ts-morph';
import type {
Parser,
ParseResult,
Expand Down Expand Up @@ -118,10 +118,10 @@ export class ReactParser implements Parser {
const type = firstParam.getType();
const properties = type.getProperties();

properties.forEach(prop => {
properties.forEach((prop: Symbol) => {
const jsDocs = prop.getJsDocTags();
const descriptionTag = jsDocs.find(tag => !tag.getName());
const description = descriptionTag ? descriptionTag.getText().map(p => p.text).join('') : undefined;
const descriptionTag = jsDocs.find((tag: JSDocTagInfo) => !tag.getName());
const description = descriptionTag ? descriptionTag.getText().map((p: { text: string }) => p.text).join('') : undefined;

props.push({
name: prop.getName(),
Expand Down Expand Up @@ -156,7 +156,7 @@ export class ReactParser implements Parser {

if (!body) return hooks;

body.getDescendantsOfKind(SyntaxKind.Identifier).forEach(identifier => {
body.getDescendantsOfKind(SyntaxKind.Identifier).forEach((identifier: Identifier) => {
const name = identifier.getText();
if (name.startsWith('use') && /[A-Z]/.test(name[3] || '')) {
hooks.push({ name });
Expand Down
38 changes: 31 additions & 7 deletions services/core/scanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import { statSync } from 'fs';
import { resolve, relative, basename, dirname, extname } from 'path';
import { getFrameworkPatterns, DEFAULT_EXCLUDE_PATTERNS } from './patterns.js';
import type { ScanOptions, ScanResult, ComponentFile } from '../types/index.js';

import { Project } from 'ts-morph';
import { isActuallyReactComponent } from '../utils/ast-helpers.js';
import { shouldIgnore } from '../utils/ignore.js';
export class ComponentScanner {
private options: Required<ScanOptions>;

Expand All @@ -27,10 +29,7 @@ export class ComponentScanner {

async scan(): Promise<ScanResult> {
const startTime = Date.now();
const ignoreHandler = new IgnoreHandler(
this.options.directory,
this.options.respectGitignore,
);


// build exclude patterns
const excludePatterns = [...this.options.exclude];
Expand All @@ -54,8 +53,33 @@ export class ComponentScanner {

// filter using gitignore if enabled
const filteredFiles = this.options.respectGitignore
? ignoreHandler.filter(foundFiles)
: foundFiles;
? foundFiles.filter(file => !shouldIgnore(resolve(this.options.directory, file), this.options.directory))
: foundFiles;
const project = new Project();
const verifiedComponentFiles: string[] = [];
let astIgnoredCount = 0;

if(this.options.verbose){
console.log('Verifying components with AST analysis...');
}
for (const file of filteredFiles){
const absolutePath = resolve(this.options.directory, file);

try{
const sourceFile = project.addSourceFileAtPath(absolutePath);
//test if actaully returns jsx element
if (isActuallyReactComponent(sourceFile)){
verifiedComponentFiles.push(file);
}else{
astIgnoredCount++;
}
project.removeSourceFile(sourceFile);
}catch (err) {
if (this.options.verbose) console.warn(`Skipping unparsable file: ${file}`);
astIgnoredCount++;
}

}

// convert to ComponentFile objects
const componentFiles: ComponentFile[] = filteredFiles.map((file: string) => {
Expand Down
3 changes: 2 additions & 1 deletion services/core/watcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import chokidar, { FSWatcher } from 'chokidar';
import { parserFactory } from './parsers/index.js';
import type { ComponentMetadata, ParseOptions } from '../types/parser.js';
import { DependencyGraphBuilder } from './dependency-graph.js';
import { shouldIgnore } from '../utils/ignore.ts';
import { shouldIgnore } from '../utils/ignore.js';
import * as path from 'node:path';
export interface WatchOptions {
directory: string;
patterns?: string[];
Expand Down
Loading