@tscircuit/cli 0.0.57 → 0.0.59

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.
@@ -8,7 +8,7 @@ import { getDevServerAxios } from "./get-dev-server-axios"
8
8
  import { uploadExamplesFromDirectory } from "./upload-examples-from-directory"
9
9
  import { unlink } from "fs/promises"
10
10
  import * as Path from "path"
11
- import { startWatcher } from "./start-watcher"
11
+ import { startFsWatcher } from "./start-fs-watcher"
12
12
  import { createOrModifyNpmrc } from "../init/create-or-modify-npmrc"
13
13
  import { checkIfInitialized } from "./check-if-initialized"
14
14
  import { initCmd } from "../init"
@@ -74,7 +74,7 @@ export const devCmd = async (ctx: AppContext, args: any) => {
74
74
  await uploadExamplesFromDirectory({ devServerAxios, cwd }, ctx)
75
75
 
76
76
  // Start watcher
77
- const watcher = await startWatcher({ cwd, devServerAxios }, ctx)
77
+ const watcher = await startFsWatcher({ cwd, devServerAxios }, ctx)
78
78
 
79
79
  while (true) {
80
80
  const { action } = await prompts({
@@ -3,7 +3,7 @@ import chokidar from "chokidar"
3
3
  import { uploadExamplesFromDirectory } from "./upload-examples-from-directory"
4
4
  import kleur from "kleur"
5
5
 
6
- export const startWatcher = async (
6
+ export const startFsWatcher = async (
7
7
  {
8
8
  cwd,
9
9
  devServerAxios,
@@ -2,25 +2,56 @@ import { AppContext } from "../util/app-context"
2
2
  import { z } from "zod"
3
3
  import { getDevServerAxios } from "./dev/get-dev-server-axios"
4
4
  import { uploadExamplesFromDirectory } from "./dev/upload-examples-from-directory"
5
- import { startWatcher } from "./dev/start-watcher"
5
+ import { startFsWatcher } from "./dev/start-fs-watcher"
6
+ import kleur from "kleur"
7
+ import { AxiosInstance } from "axios"
6
8
 
7
9
  export const devServerUpload = async (ctx: AppContext, args: any) => {
8
10
  const params = z
9
11
  .object({
10
12
  dir: z.string().optional().default(ctx.cwd),
11
- port: z.coerce.number().optional().default(3020),
13
+ port: z.coerce.number().optional().nullable().default(null),
12
14
  watch: z.boolean().optional().default(false),
13
15
  })
14
16
  .parse(args)
15
17
 
16
- const serverUrl = `http://localhost:${params.port}`
17
- const devServerAxios = getDevServerAxios({ serverUrl })
18
+ let serverUrl = `http://localhost:${params.port ?? 3020}`
19
+ let devServerAxios = getDevServerAxios({ serverUrl })
20
+
21
+ const checkHealth = () =>
22
+ devServerAxios
23
+ .get("/api/health")
24
+ .then(() => true)
25
+ .catch((e) => false)
26
+
27
+ let is_dev_server_healthy = await checkHealth()
28
+
29
+ if (!is_dev_server_healthy && !params.port) {
30
+ // attempt to use development-mode port, e.g. if someone ran
31
+ // npm run start:dev-server:dev
32
+ const devModeServerUrl = "http://localhost:3021"
33
+ devServerAxios = getDevServerAxios({ serverUrl: devModeServerUrl })
34
+ is_dev_server_healthy = await checkHealth()
35
+ if (is_dev_server_healthy) serverUrl = devModeServerUrl
36
+ }
37
+
38
+ if (!is_dev_server_healthy) {
39
+ console.log(
40
+ kleur.red(
41
+ `Dev server doesn't seem to be running at ${serverUrl}. (Could not ping health)`
42
+ )
43
+ )
44
+ process.exit(1)
45
+ }
18
46
 
19
47
  console.log(`Loading examples...`)
20
48
  await uploadExamplesFromDirectory({ devServerAxios, cwd: params.dir }, ctx)
21
49
 
22
50
  if (params.watch) {
23
51
  // Start watcher
24
- const watcher = await startWatcher({ cwd: params.dir, devServerAxios }, ctx)
52
+ const watcher = await startFsWatcher(
53
+ { cwd: params.dir, devServerAxios },
54
+ ctx
55
+ )
25
56
  }
26
57
  }
@@ -0,0 +1,22 @@
1
+ import { AppContext } from "../util/app-context"
2
+ import { z } from "zod"
3
+ import { exportGerbersToFile } from "lib/export-gerbers"
4
+
5
+ export const exportGerbersCmd = async (ctx: AppContext, args: any) => {
6
+ const params = z
7
+ .object({
8
+ file: z.string(),
9
+ export: z.string().optional(),
10
+ outputfile: z.string().optional().default("gerbers.zip"),
11
+ })
12
+ .parse(args)
13
+
14
+ await exportGerbersToFile(
15
+ {
16
+ example_file_path: params.file,
17
+ export_name: params.export,
18
+ output_zip_path: params.outputfile,
19
+ },
20
+ ctx
21
+ )
22
+ }
@@ -36,3 +36,4 @@ export { devServerUpload } from "./dev-server-upload"
36
36
  export { configClear } from "./config-clear"
37
37
  export { openCmd as open } from "./open"
38
38
  export { versionCmd as version } from "./version"
39
+ export { exportGerbersCmd as exportGerbers } from "./export-gerbers"
@@ -0,0 +1,84 @@
1
+ import { AppContext } from "./util/app-context"
2
+ import { z } from "zod"
3
+ import * as Path from "path"
4
+ import { unlink } from "node:fs/promises"
5
+ import { soupify } from "lib/soupify"
6
+ import * as fs from "fs"
7
+ import {
8
+ stringifyGerberCommandLayers,
9
+ convertSoupToGerberCommands,
10
+ } from "@tscircuit/builder"
11
+ import kleur from "kleur"
12
+ import archiver from "archiver"
13
+
14
+ export const exportGerbersToFile = async (
15
+ params: {
16
+ example_file_path: string
17
+ export_name?: string
18
+ output_zip_path: string
19
+ },
20
+ ctx: AppContext
21
+ ) => {
22
+ console.log(kleur.gray("[soupifying]..."))
23
+ const soup = await soupify(
24
+ {
25
+ filePath: params.example_file_path,
26
+ exportName: params.export_name,
27
+ },
28
+ ctx
29
+ )
30
+
31
+ console.log(kleur.gray("[soup to gerber json]..."))
32
+ const gerber_layer_cmds = convertSoupToGerberCommands(soup)
33
+
34
+ console.log(kleur.gray("[stringify gerber json]..."))
35
+ const gerber_file_contents = stringifyGerberCommandLayers(gerber_layer_cmds)
36
+
37
+ console.log(kleur.gray("[writing gerbers to tmp dir]..."))
38
+ const tempDir = Path.join(".tscircuit", "tmp-gerber-export")
39
+ fs.mkdirSync(tempDir, { recursive: true })
40
+ for (const [fileName, fileContents] of Object.entries(gerber_file_contents)) {
41
+ const filePath = Path.join(tempDir, fileName)
42
+ await fs.writeFileSync(filePath, fileContents)
43
+ }
44
+
45
+ console.log(kleur.gray("[zipping tmp dir]..."))
46
+ const output = fs.createWriteStream(params.output_zip_path)
47
+ const archive = archiver("zip", {
48
+ zlib: { level: 9 },
49
+ })
50
+
51
+ archive.pipe(output)
52
+ archive.directory(tempDir, false)
53
+
54
+ await new Promise((resolve, reject) => {
55
+ output.on("close", resolve)
56
+ output.on("error", reject)
57
+ })
58
+ }
59
+
60
+ export const exportGerbersToZipBuffer = async (
61
+ params: {
62
+ example_file_path: string
63
+ export_name?: string
64
+ },
65
+ ctx: AppContext
66
+ ) => {
67
+ const tempDir = Path.join(".tscircuit", "tmp-gerber-zip")
68
+ fs.mkdirSync(tempDir, { recursive: true })
69
+
70
+ await exportGerbersToFile(
71
+ {
72
+ example_file_path: params.example_file_path,
73
+ export_name: params.export_name,
74
+ output_zip_path: Path.join(tempDir, "gerbers.zip"),
75
+ },
76
+ ctx
77
+ )
78
+
79
+ const buffer = fs.readFileSync(Path.join(tempDir, "gerbers.zip"))
80
+
81
+ fs.rmSync(tempDir, { recursive: true })
82
+
83
+ return buffer
84
+ }
@@ -251,6 +251,21 @@ export const getProgram = (ctx: AppContext) => {
251
251
  .description("Clear your local authentication")
252
252
  .action((args) => CMDFN.authLogout(ctx, args))
253
253
 
254
+ const exportCmd = cmd
255
+ .command("export")
256
+ .description("Export Gerbers, Drill Files, Netlists and more")
257
+
258
+ exportCmd
259
+ .command("gerbers")
260
+ .description("Export Gerber files from an example file")
261
+ .requiredOption("--file <file>", "Input example files")
262
+ .option(
263
+ "--export <export_name>",
264
+ "Name of export to soupify, if not specified, soupify the default/only export"
265
+ )
266
+ .option("--outputfile <outputfile>", "Output file name", "gerbers.zip")
267
+ .action((args) => CMDFN.exportGerbers(ctx, args))
268
+
254
269
  cmd
255
270
  .command("soupify")
256
271
  .description("Convert an example file to tscircuit soup")
package/lib/soupify.ts CHANGED
@@ -5,6 +5,7 @@ import * as Path from "path"
5
5
  import { unlink } from "node:fs/promises"
6
6
  import kleur from "kleur"
7
7
  import { writeFileSync } from "fs"
8
+ import { readFile } from "fs/promises"
8
9
 
9
10
  export const soupify = async (
10
11
  {
@@ -16,6 +17,27 @@ export const soupify = async (
16
17
  },
17
18
  ctx: { runtime: "node" | "bun" }
18
19
  ) => {
20
+ const targetFileContent = await readFile(filePath, "utf-8")
21
+
22
+ if (!exportName) {
23
+ if (targetFileContent.includes("export default")) {
24
+ exportName = "default"
25
+ } else {
26
+ // Look for "export const <name>" or "export function <name>"
27
+ const exportRegex = /export\s+(?:const|function)\s+(\w+)/g
28
+ const match = exportRegex.exec(targetFileContent)
29
+ if (match) {
30
+ exportName = match[1]
31
+ }
32
+ }
33
+ }
34
+
35
+ if (!exportName) {
36
+ throw new Error(
37
+ `Couldn't derive an export name and didn't find default export in "${filePath}"`
38
+ )
39
+ }
40
+
19
41
  const tmpFilePath = Path.join(
20
42
  Path.dirname(filePath),
21
43
  Path.basename(filePath).replace(/\.[^\.]+$/, "") + ".__tmp_entrypoint.tsx"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tscircuit/cli",
3
- "version": "0.0.57",
3
+ "version": "0.0.59",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Command line tool for developing, publishing and installing tscircuit circuits",
@@ -37,6 +37,7 @@
37
37
  "@hono/node-server": "^1.8.2",
38
38
  "@tscircuit/builder": "latest",
39
39
  "@tscircuit/react-fiber": "latest",
40
+ "archiver": "^7.0.1",
40
41
  "axios": "^1.6.7",
41
42
  "better-sqlite3": "^9.4.3",
42
43
  "chokidar": "^3.6.0",
@@ -64,6 +65,7 @@
64
65
  "zod": "latest"
65
66
  },
66
67
  "devDependencies": {
68
+ "@types/archiver": "^6.0.2",
67
69
  "@types/bun": "^1.0.8",
68
70
  "@types/chokidar": "^2.1.3",
69
71
  "@types/configstore": "^6.0.2",
@@ -1,5 +1,5 @@
1
1
  import "@tscircuit/react-fiber"
2
2
 
3
3
  export const MyCircuit = () => (
4
- <resistor name="R2" resistance="20kohm" footprint="0805" />
4
+ <resistor name="R2" resistance="20kohm" footprint="0402" />
5
5
  )