Skip to content

Commit

Permalink
checkpoint, basic stuff working
Browse files Browse the repository at this point in the history
  • Loading branch information
seveibar committed Dec 15, 2024
1 parent f8d2cb3 commit b52f617
Show file tree
Hide file tree
Showing 10 changed files with 328 additions and 121 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -174,4 +174,6 @@ dist
# Finder (MacOS) folder config
.DS_Store

.vscode
.vscode
.aider*
package-lock.json
49 changes: 49 additions & 0 deletions biome.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"$schema": "https://biomejs.dev/schemas/1.9.3/schema.json",
"organizeImports": {
"enabled": true
},
"formatter": {
"enabled": true,
"indentStyle": "space"
},
"files": {
"ignore": ["cosmos-export", "dist", "package.json"]
},
"javascript": {
"formatter": {
"jsxQuoteStyle": "double",
"quoteProperties": "asNeeded",
"trailingCommas": "all",
"semicolons": "asNeeded",
"arrowParentheses": "always",
"bracketSpacing": true,
"bracketSameLine": false
}
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"suspicious": {
"noExplicitAny": "off"
},
"complexity": {
"noForEach": "info"
},
"style": {
"noUselessElse": "off",
"noNonNullAssertion": "off",
"useNumberNamespace": "off",
"useFilenamingConvention": {
"level": "error",
"options": {
"strictCase": true,
"requireAscii": true,
"filenameCases": ["kebab-case", "export"]
}
}
}
}
}
}
Binary file modified bun.lockb
Binary file not shown.
79 changes: 79 additions & 0 deletions cli/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#!/usr/bin/env node
import { Command } from "commander"
import * as path from "path"
import * as chokidar from "chokidar"
import * as fs from "fs"
import { createServer } from "../lib/server/createServer"
import { getLocalFileDependencies } from "../lib/dependency-analysis/getLocalFileDependencies"

const program = new Command()

program
.name("snippets")
.description("CLI for developing tscircuit snippets")
.version("1.0.0")

program
.command("dev")
.description("Start development server for a snippet")
.argument("<file>", "Path to the snippet file")
.option("-p, --port <number>", "Port to run server on", "3000")
.action(async (file: string, options: { port: string }) => {
const absolutePath = path.resolve(file)
const port = parseInt(options.port)

// Start the server
await createServer(port)

// Function to update file content
const updateFile = async (filePath: string) => {
try {
const content = await fs.promises.readFile(filePath, "utf-8")
const response = await fetch(
`http://localhost:${port}/api/files/upsert`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
file_path: path.relative(process.cwd(), filePath),
text_content: content,
}),
},
)
if (!response.ok) {
console.error(`Failed to update ${filePath}`)
}
} catch (error) {
console.error(`Error updating ${filePath}:`, error)
}
}

// Get initial dependencies
let dependencies = new Set([absolutePath])
try {
const deps = getLocalFileDependencies(absolutePath)
deps.forEach((dep) => dependencies.add(dep))
} catch (error) {
console.warn("Failed to analyze dependencies:", error)
}

// Watch the main file and its dependencies
const watcher = chokidar.watch(Array.from(dependencies), {
persistent: true,
ignoreInitial: false,
})

watcher.on("change", async (filePath) => {
console.log(`File ${filePath} changed`)
await updateFile(filePath)
})

watcher.on("add", async (filePath) => {
console.log(`File ${filePath} added`)
await updateFile(filePath)
})

console.log(`Watching ${file} and its dependencies...`)
})

program.parse()
18 changes: 18 additions & 0 deletions example-dir/snippet.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { useRedLed } from "@tsci/seveibar.red-led"
import { usePushButton } from "@tsci/seveibar.push-button"
import { useUsbC } from "@tsci/seveibar.smd-usb-c"

export default () => {
const USBC = useUsbC("USBC")
const Button = usePushButton("SW1")
const Led = useRedLed("LED")
return (
<board width="12mm" height="30mm" schAutoLayoutEnabled>
<USBC GND="net.GND" pcbY={-10} VBUS1="net.VBUS" />
<Led neg="net.GND" pcbY={12} />
<Button pcbY={0} pin2=".R1 > .pos" pin3="net.VBUS" />
<resistor name="R1" footprint="0603" resistance="1k" pcbY={7} />
<trace from=".R1 > .neg" to={Led.pos} />
</board>
)
}
104 changes: 53 additions & 51 deletions lib/dependency-analysis/DependencyAnalyzer.ts
Original file line number Diff line number Diff line change
@@ -1,127 +1,129 @@
import * as ts from 'typescript';
import * as path from 'path';
import * as fs from 'fs';
import * as ts from "typescript"
import * as path from "path"
import * as fs from "fs"

interface DependencyAnalyzerOptions {
baseDir: string;
includeNodeModules?: boolean;
includeCssModules?: boolean;
baseDir: string
includeNodeModules?: boolean
includeCssModules?: boolean
}

class DependencyAnalyzer {
private readonly baseDir: string;
private readonly includeNodeModules: boolean;
private readonly includeCssModules: boolean;
private readonly cache: Map<string, Set<string>> = new Map();
private readonly baseDir: string
private readonly includeNodeModules: boolean
private readonly includeCssModules: boolean
private readonly cache: Map<string, Set<string>> = new Map()

constructor(options: DependencyAnalyzerOptions) {
this.baseDir = options.baseDir;
this.includeNodeModules = options.includeNodeModules ?? false;
this.includeCssModules = options.includeCssModules ?? true;
this.baseDir = options.baseDir
this.includeNodeModules = options.includeNodeModules ?? false
this.includeCssModules = options.includeCssModules ?? true
}

public analyze(filePath: string): Set<string> {
if (this.cache.has(filePath)) {
return this.cache.get(filePath)!;
return this.cache.get(filePath)!
}

const dependencies = new Set<string>();
const sourceFile = this.createSourceFile(filePath);
const dependencies = new Set<string>()
const sourceFile = this.createSourceFile(filePath)

if (!sourceFile) {
return dependencies;
return dependencies
}

this.visitNode(sourceFile, dependencies);
this.cache.set(filePath, dependencies);
this.visitNode(sourceFile, dependencies)
this.cache.set(filePath, dependencies)

return dependencies;
return dependencies
}

private createSourceFile(filePath: string): ts.SourceFile | null {
try {
const content = fs.readFileSync(filePath, 'utf-8');
const content = fs.readFileSync(filePath, "utf-8")
return ts.createSourceFile(
filePath,
content,
ts.ScriptTarget.Latest,
true
);
true,
)
} catch (error) {
console.error(`Error reading file ${filePath}:`, error);
return null;
console.error(`Error reading file ${filePath}:`, error)
return null
}
}

private visitNode(node: ts.Node, dependencies: Set<string>): void {
if (ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) {
const moduleSpecifier = node.moduleSpecifier;
const moduleSpecifier = node.moduleSpecifier
if (moduleSpecifier && ts.isStringLiteral(moduleSpecifier)) {
const importPath = moduleSpecifier.text;
this.addDependency(importPath, dependencies);
const importPath = moduleSpecifier.text
this.addDependency(importPath, dependencies)
}
}

// Check for dynamic imports
if (ts.isCallExpression(node) &&
node.expression.kind === ts.SyntaxKind.ImportKeyword) {
const argument = node.arguments[0];
if (
ts.isCallExpression(node) &&
node.expression.kind === ts.SyntaxKind.ImportKeyword
) {
const argument = node.arguments[0]
if (argument && ts.isStringLiteral(argument)) {
this.addDependency(argument.text, dependencies);
this.addDependency(argument.text, dependencies)
}
}

ts.forEachChild(node, child => this.visitNode(child, dependencies));
ts.forEachChild(node, (child) => this.visitNode(child, dependencies))
}

private addDependency(importPath: string, dependencies: Set<string>): void {
// Skip node_modules unless explicitly included
if (!this.includeNodeModules && importPath.startsWith('node_modules')) {
return;
if (!this.includeNodeModules && importPath.startsWith("node_modules")) {
return
}

// Handle CSS modules if enabled
if (this.includeCssModules && importPath.endsWith('.css')) {
const resolvedPath = this.resolvePath(importPath);
if (this.includeCssModules && importPath.endsWith(".css")) {
const resolvedPath = this.resolvePath(importPath)
if (resolvedPath) {
dependencies.add(resolvedPath);
dependencies.add(resolvedPath)
}
return;
return
}

// Resolve relative imports
if (importPath.startsWith('.')) {
const resolvedPath = this.resolvePath(importPath);
if (importPath.startsWith(".")) {
const resolvedPath = this.resolvePath(importPath)
if (resolvedPath) {
dependencies.add(resolvedPath);
dependencies.add(resolvedPath)
}
}
}

private resolvePath(importPath: string): string | null {
try {
const extensions = ['.tsx', '.ts', '.jsx', '.js', '.css'];
const resolvedPath = path.resolve(this.baseDir, importPath);
const extensions = [".tsx", ".ts", ".jsx", ".js", ".css"]
const resolvedPath = path.resolve(this.baseDir, importPath)

// Try exact path first
if (fs.existsSync(resolvedPath)) {
return resolvedPath;
return resolvedPath
}

// Try with extensions
for (const ext of extensions) {
const pathWithExt = resolvedPath + ext;
const pathWithExt = resolvedPath + ext
if (fs.existsSync(pathWithExt)) {
return pathWithExt;
return pathWithExt
}
}

return null;
return null
} catch (error) {
console.error(`Error resolving path ${importPath}:`, error);
return null;
console.error(`Error resolving path ${importPath}:`, error)
return null
}
}
}

export { DependencyAnalyzer, type DependencyAnalyzerOptions };
export { DependencyAnalyzer, type DependencyAnalyzerOptions }
Loading

0 comments on commit b52f617

Please sign in to comment.