@tscircuit/cli 0.0.64 → 0.0.66
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/bun.lockb +0 -0
- package/dev-server-api/src/lib/zod/export_parameters.ts +7 -0
- package/dev-server-api/src/lib/zod/export_request.ts +2 -0
- package/dev-server-frontend/bun.lockb +0 -0
- package/dev-server-frontend/src/HeaderMenu.tsx +26 -6
- package/dev-server-frontend/src/components/dialogs/generic-export-dialog.tsx +188 -0
- package/dev-server-frontend/tsconfig.json +3 -0
- package/dist/cli.js +143 -56
- package/lib/cmd-fns/dev/fulfill-export-requests.ts +90 -26
- package/lib/cmd-fns/export-gerbers.ts +1 -1
- package/lib/export-fns/export-bom-csv.ts +30 -0
- package/lib/{export-gerbers.ts → export-fns/export-gerbers.ts} +1 -1
- package/lib/export-fns/export-pnp-csv.ts +35 -0
- package/lib/soupify.ts +11 -2
- package/package.json +5 -3
- package/tests/assets/example-project/package-lock.json +28 -9
- package/tests/assets/example-project/package.json +2 -2
- package/tests/assets/example-project/src/MyCircuit.tsx +8 -1
- package/tsconfig.json +3 -1
package/bun.lockb
CHANGED
|
Binary file
|
|
@@ -2,9 +2,16 @@ import { z } from "zod"
|
|
|
2
2
|
|
|
3
3
|
export const export_parameters = z.object({
|
|
4
4
|
should_export_gerber_zip: z.boolean().default(false),
|
|
5
|
+
should_export_pnp_csv: z.boolean().default(false),
|
|
6
|
+
should_export_bom_csv: z.boolean().default(false),
|
|
5
7
|
gerbers_zip_file_name: z
|
|
6
8
|
.string()
|
|
7
9
|
.nullable()
|
|
8
10
|
.optional()
|
|
9
11
|
.default("gerbers.zip"),
|
|
12
|
+
pnp_csv_file_name: z.string().nullable().optional().default("pnp.csv"),
|
|
13
|
+
bom_csv_file_name: z.string().nullable().optional().default("bom.csv"),
|
|
10
14
|
})
|
|
15
|
+
|
|
16
|
+
export type ExportParametersInput = z.input<typeof export_parameters>
|
|
17
|
+
export type ExportParameters = z.infer<typeof export_parameters>
|
|
Binary file
|
|
@@ -18,6 +18,7 @@ import { useGlobalStore } from "./hooks/use-global-store"
|
|
|
18
18
|
import packageJson from "../package.json"
|
|
19
19
|
import cliPackageJson from "../../package.json"
|
|
20
20
|
import { useGerberExportDialog } from "./components/dialogs/gerber-export-dialog"
|
|
21
|
+
import { useGenericExportDialog } from "./components/dialogs/generic-export-dialog"
|
|
21
22
|
|
|
22
23
|
export const HeaderMenu = () => {
|
|
23
24
|
const [viewMode, setViewMode] = useGlobalStore((s) => [
|
|
@@ -30,6 +31,24 @@ export const HeaderMenu = () => {
|
|
|
30
31
|
])
|
|
31
32
|
const [inDebugMode, setInDebugMode] = useState(false)
|
|
32
33
|
const gerberExportDialog = useGerberExportDialog()
|
|
34
|
+
const pnpExportDialog = useGenericExportDialog({
|
|
35
|
+
dialogTitle: "Export Pick'n'Place",
|
|
36
|
+
dialogDescription:
|
|
37
|
+
"Export the Pick'n'Place CSV for this example export. You can upload this to an assembler (PCBA) to tell their machines where to place each component.",
|
|
38
|
+
exportFileName: "pnp.csv",
|
|
39
|
+
exportParameters: {
|
|
40
|
+
should_export_pnp_csv: true,
|
|
41
|
+
},
|
|
42
|
+
})
|
|
43
|
+
const bomExportDialog = useGenericExportDialog({
|
|
44
|
+
dialogTitle: "Export Bill of Materials",
|
|
45
|
+
dialogDescription:
|
|
46
|
+
"Export the Bill of Materials CSV for this example export. You can upload this to an assembler (PCBA) so they know how to source each component.",
|
|
47
|
+
exportFileName: "bom.csv",
|
|
48
|
+
exportParameters: {
|
|
49
|
+
should_export_bom_csv: true,
|
|
50
|
+
},
|
|
51
|
+
})
|
|
33
52
|
|
|
34
53
|
return (
|
|
35
54
|
<>
|
|
@@ -53,11 +72,10 @@ export const HeaderMenu = () => {
|
|
|
53
72
|
<MenubarItem onSelect={() => gerberExportDialog.openDialog()}>
|
|
54
73
|
Gerbers
|
|
55
74
|
</MenubarItem>
|
|
56
|
-
<MenubarItem
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
>
|
|
75
|
+
<MenubarItem onSelect={() => pnpExportDialog.openDialog()}>
|
|
76
|
+
Pick'n'Place CSV
|
|
77
|
+
</MenubarItem>
|
|
78
|
+
<MenubarItem onSelect={() => bomExportDialog.openDialog()}>
|
|
61
79
|
Bill of Materials
|
|
62
80
|
</MenubarItem>
|
|
63
81
|
<MenubarItem
|
|
@@ -234,7 +252,9 @@ export const HeaderMenu = () => {
|
|
|
234
252
|
</MenubarContent>
|
|
235
253
|
</MenubarMenu>
|
|
236
254
|
</Menubar>
|
|
237
|
-
|
|
255
|
+
<gerberExportDialog.Component />
|
|
256
|
+
<pnpExportDialog.Component />
|
|
257
|
+
<bomExportDialog.Component />
|
|
238
258
|
</>
|
|
239
259
|
)
|
|
240
260
|
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { useMemo, useState } from "react"
|
|
2
|
+
import { Dialog } from "@headlessui/react"
|
|
3
|
+
import { useGlobalStore } from "src/hooks/use-global-store"
|
|
4
|
+
import { useActiveDevPackageExampleLite } from "src/hooks/use-active-dev-package-example-lite"
|
|
5
|
+
import { Button } from "../ui/button"
|
|
6
|
+
import axios from "axios"
|
|
7
|
+
import {
|
|
8
|
+
ExportParameters,
|
|
9
|
+
ExportParametersInput,
|
|
10
|
+
export_parameters,
|
|
11
|
+
} from "@server/lib/zod/export_parameters"
|
|
12
|
+
|
|
13
|
+
export const useGenericExportDialog = ({
|
|
14
|
+
exportFileName,
|
|
15
|
+
dialogTitle,
|
|
16
|
+
dialogDescription,
|
|
17
|
+
exportParameters,
|
|
18
|
+
}: {
|
|
19
|
+
exportFileName: string
|
|
20
|
+
dialogTitle: string
|
|
21
|
+
dialogDescription: string
|
|
22
|
+
exportParameters: ExportParametersInput
|
|
23
|
+
}) => {
|
|
24
|
+
const [open, setIsOpen] = useState(false)
|
|
25
|
+
const activeDevExamplePackage = useActiveDevPackageExampleLite()
|
|
26
|
+
const [isExporting, setIsExporting] = useState(false)
|
|
27
|
+
const [exportError, setExportError] = useState<string | null>(null)
|
|
28
|
+
|
|
29
|
+
return useMemo(() => {
|
|
30
|
+
const openDialog = () => {
|
|
31
|
+
setIsOpen(true)
|
|
32
|
+
}
|
|
33
|
+
const closeDialog = () => {
|
|
34
|
+
setIsOpen(false)
|
|
35
|
+
setIsExporting(false)
|
|
36
|
+
}
|
|
37
|
+
if (!activeDevExamplePackage)
|
|
38
|
+
return {
|
|
39
|
+
openDialog,
|
|
40
|
+
closeDialog,
|
|
41
|
+
Component: () => null,
|
|
42
|
+
}
|
|
43
|
+
const { file_path: inputFilePath, export_name: exampleExportName } =
|
|
44
|
+
activeDevExamplePackage
|
|
45
|
+
const outputName = `${
|
|
46
|
+
inputFilePath.split("/").pop()?.split(".")[0]
|
|
47
|
+
}-${exampleExportName}-${exportFileName}`
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
openDialog,
|
|
51
|
+
closeDialog,
|
|
52
|
+
Component: () => (
|
|
53
|
+
<GenericExportDialog
|
|
54
|
+
dialogTitle={dialogTitle}
|
|
55
|
+
dialogDescription={dialogDescription}
|
|
56
|
+
open={open}
|
|
57
|
+
isExporting={isExporting}
|
|
58
|
+
filePath={inputFilePath}
|
|
59
|
+
tsCircuitExportName={exampleExportName}
|
|
60
|
+
outputName={outputName}
|
|
61
|
+
exportError={exportError}
|
|
62
|
+
onClickExport={async () => {
|
|
63
|
+
setExportError(null)
|
|
64
|
+
setIsExporting(true)
|
|
65
|
+
try {
|
|
66
|
+
if (exportParameters.should_export_pnp_csv) {
|
|
67
|
+
exportParameters.pnp_csv_file_name ??= exportFileName
|
|
68
|
+
}
|
|
69
|
+
if (exportParameters.should_export_bom_csv) {
|
|
70
|
+
exportParameters.bom_csv_file_name ??= exportFileName
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
let export_request = await axios
|
|
74
|
+
.post("/api/export_requests/create", {
|
|
75
|
+
example_file_path: activeDevExamplePackage.file_path,
|
|
76
|
+
export_name: activeDevExamplePackage.export_name,
|
|
77
|
+
export_parameters: exportParameters,
|
|
78
|
+
})
|
|
79
|
+
.then((r) => r.data.export_request)
|
|
80
|
+
const pollExportRequest = async () => {
|
|
81
|
+
while (!export_request.is_complete) {
|
|
82
|
+
try {
|
|
83
|
+
export_request = await axios
|
|
84
|
+
.post("/api/export_requests/get", {
|
|
85
|
+
export_request_id: export_request.export_request_id,
|
|
86
|
+
})
|
|
87
|
+
.then((r) => r.data.export_request)
|
|
88
|
+
} catch (e: any) {
|
|
89
|
+
console.error(e)
|
|
90
|
+
setExportError(
|
|
91
|
+
`${e.toString()}\n\n${e.response?.data?.error?.message}`
|
|
92
|
+
)
|
|
93
|
+
setIsExporting(false)
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
97
|
+
}
|
|
98
|
+
setIsExporting(false)
|
|
99
|
+
}
|
|
100
|
+
await pollExportRequest()
|
|
101
|
+
// open /api/export_files/download?export_file_id=... in new tab
|
|
102
|
+
const export_file_id =
|
|
103
|
+
export_request.file_summary[0].export_file_id
|
|
104
|
+
window.open(
|
|
105
|
+
`/api/export_files/download?export_file_id=${export_file_id}`,
|
|
106
|
+
"_blank"
|
|
107
|
+
)
|
|
108
|
+
setIsExporting(false)
|
|
109
|
+
} catch (e: any) {
|
|
110
|
+
console.error(e)
|
|
111
|
+
setExportError(
|
|
112
|
+
`${e.toString()}\n\n${e.response?.data?.error?.message}`
|
|
113
|
+
)
|
|
114
|
+
setIsExporting(false)
|
|
115
|
+
}
|
|
116
|
+
}}
|
|
117
|
+
onClose={() => closeDialog()}
|
|
118
|
+
/>
|
|
119
|
+
),
|
|
120
|
+
}
|
|
121
|
+
}, [open, isExporting, exportError])
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export const GenericExportDialog = ({
|
|
125
|
+
open,
|
|
126
|
+
dialogTitle,
|
|
127
|
+
dialogDescription,
|
|
128
|
+
isExporting,
|
|
129
|
+
filePath,
|
|
130
|
+
tsCircuitExportName,
|
|
131
|
+
outputName,
|
|
132
|
+
exportError,
|
|
133
|
+
onClose,
|
|
134
|
+
onClickExport,
|
|
135
|
+
}: {
|
|
136
|
+
open: boolean
|
|
137
|
+
dialogTitle: string
|
|
138
|
+
dialogDescription: string
|
|
139
|
+
filePath: string
|
|
140
|
+
tsCircuitExportName: string
|
|
141
|
+
isExporting: boolean
|
|
142
|
+
outputName: string
|
|
143
|
+
exportError?: string | null
|
|
144
|
+
onClose: (value: boolean) => void
|
|
145
|
+
onClickExport: () => any
|
|
146
|
+
}) => {
|
|
147
|
+
return (
|
|
148
|
+
<Dialog open={open} onClose={onClose} className="relative z-50">
|
|
149
|
+
<div className="fixed inset-0 bg-black/30" aria-hidden="true" />
|
|
150
|
+
<div className="fixed inset-0 flex w-screen items-center justify-center p-4">
|
|
151
|
+
<Dialog.Panel className="w-full max-w-lg rounded bg-white p-6 border border-[rgba(0,0,0,0.3)]">
|
|
152
|
+
<Dialog.Title className="text-lg font-semibold tracking-tight text-black">
|
|
153
|
+
{dialogTitle}
|
|
154
|
+
</Dialog.Title>
|
|
155
|
+
<Dialog.Description className="text-sm font-medium text-muted-foreground">
|
|
156
|
+
{dialogDescription}
|
|
157
|
+
</Dialog.Description>
|
|
158
|
+
{exportError && (
|
|
159
|
+
<div className="text-red-700 bg-red-100 border-red-200 border whitespace-pre-wrap font-mono text-xs p-4 mt-8">
|
|
160
|
+
{exportError}
|
|
161
|
+
</div>
|
|
162
|
+
)}
|
|
163
|
+
<div className="text-xs font-medium grid grid-cols-4 font-mono mt-8 gap-4">
|
|
164
|
+
<div className="text-muted-foreground font-sans">Status</div>
|
|
165
|
+
<div className="col-span-3 text-muted-foreground font-sans">
|
|
166
|
+
{isExporting ? "Exporting" : "Not Started"}
|
|
167
|
+
</div>
|
|
168
|
+
<div className="text-muted-foreground font-sans">File Path</div>
|
|
169
|
+
<div className="col-span-3">{filePath.split("/examples/")[1]}</div>
|
|
170
|
+
<div className="text-muted-foreground font-sans">Export Name</div>
|
|
171
|
+
<div className="col-span-3">{tsCircuitExportName}</div>
|
|
172
|
+
<div className="text-muted-foreground font-sans">Output Name</div>
|
|
173
|
+
<div className="col-span-3">{outputName}</div>
|
|
174
|
+
</div>
|
|
175
|
+
<div className="flex mt-8 justify-end">
|
|
176
|
+
<Button
|
|
177
|
+
variant={isExporting ? "ghost" : "default"}
|
|
178
|
+
disabled={isExporting}
|
|
179
|
+
onClick={onClickExport}
|
|
180
|
+
>
|
|
181
|
+
{isExporting ? `Exporting...` : `Export`}
|
|
182
|
+
</Button>
|
|
183
|
+
</div>
|
|
184
|
+
</Dialog.Panel>
|
|
185
|
+
</div>
|
|
186
|
+
</Dialog>
|
|
187
|
+
)
|
|
188
|
+
}
|