@tscircuit/cli 0.1.4 → 0.1.6

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/biome.json CHANGED
@@ -32,6 +32,7 @@
32
32
  "noForEach": "off"
33
33
  },
34
34
  "style": {
35
+ "noUnusedTemplateLiteral": "off",
35
36
  "noUselessElse": "off",
36
37
  "noNonNullAssertion": "off",
37
38
  "useNumberNamespace": "off",
package/bun.lockb CHANGED
Binary file
@@ -0,0 +1,158 @@
1
+ import ky from "ky"
2
+ import type { FileServerRoutes } from "lib/file-server/FileServerRoutes"
3
+ import { createHttpServer } from "lib/server/createHttpServer"
4
+ import { EventsWatcher } from "lib/server/EventsWatcher"
5
+ import type http from "node:http"
6
+ import type { TypedKyInstance } from "typed-ky"
7
+ import path from "node:path"
8
+ import fs from "node:fs"
9
+ import type { FileUpdatedEvent } from "lib/file-server/FileServerEvent"
10
+ import * as chokidar from "chokidar"
11
+
12
+ export class DevServer {
13
+ port: number
14
+ /**
15
+ * The path to a component that exports a <board /> or <group /> component
16
+ */
17
+ componentFilePath: string
18
+
19
+ projectDir: string
20
+
21
+ /**
22
+ * The HTTP server that hosts the file server and event bus. You can use
23
+ * fsKy to communicate with the file server/event bus
24
+ */
25
+ httpServer?: http.Server
26
+ /**
27
+ * Watches for events on the event bus by polling `api/events/list`
28
+ */
29
+ eventsWatcher?: EventsWatcher
30
+ /**
31
+ * A ky instance that can be used to communicate with the file server and
32
+ * event bus
33
+ */
34
+ fsKy: TypedKyInstance<keyof FileServerRoutes, FileServerRoutes>
35
+ /**
36
+ * A chokidar instance that watches the project directory for file changes
37
+ */
38
+ filesystemWatcher?: chokidar.FSWatcher
39
+
40
+ constructor({
41
+ port,
42
+ componentFilePath,
43
+ }: {
44
+ port: number
45
+ componentFilePath: string
46
+ }) {
47
+ this.port = port
48
+ this.componentFilePath = componentFilePath
49
+ this.projectDir = path.dirname(componentFilePath)
50
+ this.fsKy = ky.create({
51
+ prefixUrl: `http://localhost:${port}`,
52
+ }) as any
53
+ }
54
+
55
+ async start() {
56
+ const { server } = await createHttpServer(this.port)
57
+ this.httpServer = server
58
+
59
+ this.eventsWatcher = new EventsWatcher(`http://localhost:${this.port}`)
60
+ this.eventsWatcher.start()
61
+
62
+ this.eventsWatcher.on(
63
+ "FILE_UPDATED",
64
+ this.handleFileUpdatedEventFromServer.bind(this),
65
+ )
66
+
67
+ this.filesystemWatcher = chokidar.watch(this.projectDir, {
68
+ persistent: true,
69
+ ignoreInitial: true,
70
+ })
71
+
72
+ this.filesystemWatcher.on("change", (filePath) =>
73
+ this.handleFileChangedOnFilesystem(filePath),
74
+ )
75
+ this.filesystemWatcher.on("add", (filePath) =>
76
+ this.handleFileChangedOnFilesystem(filePath),
77
+ )
78
+
79
+ this.upsertInitialFiles()
80
+ }
81
+
82
+ async addEntrypoint() {
83
+ const relativeComponentFilePath = path.relative(
84
+ this.projectDir,
85
+ this.componentFilePath,
86
+ )
87
+ await this.fsKy.post("api/files/upsert", {
88
+ json: {
89
+ file_path: "entrypoint.tsx",
90
+ text_content: `
91
+ import MyCircuit from "./${relativeComponentFilePath}"
92
+
93
+ circuit.add(<MyCircuit />)
94
+ `,
95
+ },
96
+ })
97
+ }
98
+
99
+ async handleFileUpdatedEventFromServer(ev: FileUpdatedEvent) {
100
+ if (ev.initiator === "filesystem_change") return
101
+
102
+ if (ev.file_path === "manual-edits.json") {
103
+ console.log("Manual edits updated, updating on filesystem...")
104
+ const { file } = await this.fsKy
105
+ .get("api/files/get", {
106
+ searchParams: { file_path: ev.file_path },
107
+ })
108
+ .json()
109
+ fs.writeFileSync(
110
+ path.join(this.projectDir, "manual-edits.json"),
111
+ file.text_content,
112
+ )
113
+ }
114
+ }
115
+
116
+ async handleFileChangedOnFilesystem(absoluteFilePath: string) {
117
+ const relativeFilePath = path.relative(this.projectDir, absoluteFilePath)
118
+
119
+ // We've temporarily disabled upserting manual edits from filesystem changes
120
+ // because it can be edited by the browser
121
+ if (relativeFilePath.includes("manual-edits.json")) return
122
+
123
+ await this.fsKy
124
+ .post("api/files/upsert", {
125
+ json: {
126
+ file_path: relativeFilePath,
127
+ text_content: fs.readFileSync(absoluteFilePath, "utf-8"),
128
+ initiator: "filesystem_change",
129
+ },
130
+ })
131
+ .json()
132
+ }
133
+
134
+ async upsertInitialFiles() {
135
+ // Scan project directory for all files and upsert them
136
+ const fileNames = fs.readdirSync(this.projectDir)
137
+ for (const fileName of fileNames) {
138
+ if (fs.statSync(path.join(this.projectDir, fileName)).isDirectory())
139
+ continue
140
+ const fileContent = fs.readFileSync(
141
+ path.join(this.projectDir, fileName),
142
+ "utf-8",
143
+ )
144
+ await this.fsKy.post("api/files/upsert", {
145
+ json: {
146
+ file_path: fileName,
147
+ text_content: fileContent,
148
+ initiator: "filesystem_change",
149
+ },
150
+ })
151
+ }
152
+ }
153
+
154
+ async stop() {
155
+ this.httpServer?.close()
156
+ this.eventsWatcher?.stop()
157
+ }
158
+ }
@@ -2,10 +2,11 @@ import type { Command } from "commander"
2
2
  import * as path from "node:path"
3
3
  import * as chokidar from "chokidar"
4
4
  import * as fs from "node:fs"
5
- import { createServer } from "lib/server/createServer"
5
+ import { createHttpServer } from "lib/server/createHttpServer"
6
6
  import { getLocalFileDependencies } from "lib/dependency-analysis/getLocalFileDependencies"
7
- import { installTypes } from "../../lib/dependency-analysis/installNodeModuleTypes"
7
+ import { installNodeModuleTypesForSnippet } from "../../lib/dependency-analysis/installNodeModuleTypesForSnippet"
8
8
  import { EventsWatcher } from "../../lib/server/EventsWatcher"
9
+ import { DevServer } from "./DevServer"
9
10
 
10
11
  export const registerDev = (program: Command) => {
11
12
  program
@@ -20,92 +21,18 @@ export const registerDev = (program: Command) => {
20
21
 
21
22
  try {
22
23
  console.log("Installing types for imported snippets...")
23
- await installTypes(absolutePath)
24
+ await installNodeModuleTypesForSnippet(absolutePath)
24
25
  console.log("Types installed successfully")
25
26
  } catch (error) {
26
27
  console.warn("Failed to install types:", error)
27
28
  }
28
29
 
29
- // Start the server
30
- await createServer(port)
31
-
32
- const eventsWatcher = new EventsWatcher(`http://localhost:${port}`)
33
- eventsWatcher.start()
34
-
35
- await fetch(`http://localhost:${port}/api/files/upsert`, {
36
- method: "POST",
37
- headers: { "Content-Type": "application/json" },
38
- body: JSON.stringify({
39
- file_path: "entrypoint.tsx",
40
- text_content: `
41
- import MyCircuit from "./snippet.tsx"
42
-
43
- circuit.add(<MyCircuit />)
44
- `,
45
- }),
46
- })
47
-
48
- // Function to update file content
49
- const updateFile = async (filePath: string) => {
50
- try {
51
- const content = await fs.promises.readFile(filePath, "utf-8")
52
- const response = await fetch(
53
- `http://localhost:${port}/api/files/upsert`,
54
- {
55
- method: "POST",
56
- headers: { "Content-Type": "application/json" },
57
- body: JSON.stringify({
58
- file_path: path.relative(fileDir, filePath),
59
- text_content: content,
60
- }),
61
- },
62
- )
63
- if (!response.ok) {
64
- console.error(`Failed to update ${filePath}`)
65
- }
66
- } catch (error) {
67
- console.error(`Error updating ${filePath}:`, error)
68
- }
69
- }
70
-
71
- // Get initial dependencies
72
- const dependencies = new Set([absolutePath])
73
- try {
74
- const deps = getLocalFileDependencies(absolutePath)
75
- deps.forEach((dep) => dependencies.add(dep))
76
- } catch (error) {
77
- console.warn("Failed to analyze dependencies:", error)
78
- }
79
-
80
- // Watch the main file and its dependencies
81
- const filesystemWatcher = chokidar.watch(Array.from(dependencies), {
82
- persistent: true,
83
- ignoreInitial: false,
84
- })
85
-
86
- filesystemWatcher.on("change", async (filePath) => {
87
- console.log(`File ${filePath} changed`)
88
- await updateFile(filePath)
89
- })
90
-
91
- filesystemWatcher.on("add", async (filePath) => {
92
- console.log(`File ${filePath} added`)
93
- await updateFile(filePath)
94
- })
95
-
96
- eventsWatcher.on("FILE_UPDATED", async (ev) => {
97
- if (ev.file_path === "manual-edits.json") {
98
- console.log("Manual edits updated, updating on filesystem...")
99
- const { file } = await fetch(
100
- `http://localhost:${port}/api/files/get?file_path=manual-edits.json`,
101
- ).then((r) => r.json())
102
- fs.writeFileSync(
103
- path.join(fileDir, "manual-edits.json"),
104
- file.text_content,
105
- )
106
- }
30
+ const server = new DevServer({
31
+ port,
32
+ componentFilePath: absolutePath,
107
33
  })
108
34
 
109
- console.log(`Watching ${file} and its dependencies...`)
35
+ await server.start()
36
+ await server.addEntrypoint()
110
37
  })
111
38
  }
package/cli/main.ts CHANGED
@@ -9,13 +9,16 @@ import { registerConfigPrint } from "./config/print/register"
9
9
  import { registerClone } from "./clone/register"
10
10
  import { perfectCli } from "perfect-cli"
11
11
  import pkg from "../package.json"
12
+ import semver from "semver"
12
13
 
13
14
  const program = new Command()
14
15
 
15
16
  program
16
17
  .name("tsci")
17
18
  .description("CLI for developing tscircuit snippets")
18
- .version(pkg.version)
19
+ // HACK: at build time the version is old, we need to
20
+ // fix this at some point...
21
+ .version(semver.inc(pkg.version, "patch") ?? pkg.version)
19
22
 
20
23
  registerDev(program)
21
24
  registerClone(program)