@tscircuit/cli 0.0.4 → 0.0.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/README.md +31 -22
- package/bun.lockb +0 -0
- package/dev-server-frontend/bun.lockb +0 -0
- package/lib/cmd-fns/add.ts +34 -0
- package/lib/cmd-fns/config-reveal-location.ts +1 -1
- package/lib/cmd-fns/dev/check-if-initialized.ts +22 -0
- package/lib/cmd-fns/dev/dev-server-request-handler.ts +54 -0
- package/lib/cmd-fns/dev/get-dev-server-axios.ts +25 -0
- package/lib/cmd-fns/dev/index.ts +33 -156
- package/lib/cmd-fns/dev/infer-export-name-from-source.ts +17 -0
- package/lib/cmd-fns/dev/soupify-and-upload-example-file.ts +38 -0
- package/lib/cmd-fns/dev/start-dev-server.ts +34 -0
- package/lib/cmd-fns/dev/start-watcher.ts +48 -0
- package/lib/cmd-fns/dev/upload-examples-from-directory.ts +26 -0
- package/lib/cmd-fns/dev-server-upload.ts +26 -0
- package/lib/cmd-fns/index.ts +5 -0
- package/lib/cmd-fns/init/create-or-modify-npmrc.ts +21 -0
- package/lib/cmd-fns/init/get-generated-npmrc.ts +8 -0
- package/lib/cmd-fns/init/get-generated-readme.ts +34 -0
- package/lib/cmd-fns/init/get-generated-tsconfig.ts +34 -0
- package/lib/cmd-fns/{init.ts → init/index.ts} +19 -64
- package/lib/cmd-fns/install.ts +34 -0
- package/lib/cmd-fns/publish/index.ts +296 -0
- package/lib/cmd-fns/remove.ts +31 -0
- package/lib/cmd-fns/uninstall.ts +31 -0
- package/lib/get-program.ts +48 -7
- package/lib/util/create-context-and-run-program.ts +1 -1
- package/lib/util/get-all-package-files.ts +35 -0
- package/package.json +13 -6
- package/tests/assets/example-project/README.md +18 -0
- package/tests/assets/example-project/index.ts +1 -0
- package/tests/assets/example-project/package.json +2 -1
- package/tests/assets/example-project/src/MyCircuit.tsx +1 -1
- package/tests/init.test.ts +1 -1
- package/dist/cli.js +0 -1465
- package/lib/cmd-fns/publish.ts +0 -3
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# `
|
|
1
|
+
# `tsci` - The TSCircuit Command Line Tool
|
|
2
2
|
|
|
3
3
|
Command line tool for developing tscircuit projects and interacting with the
|
|
4
4
|
tscircuit registry.
|
|
@@ -11,39 +11,48 @@ npm install -g @tscircuit/cli
|
|
|
11
11
|
|
|
12
12
|
## Usage
|
|
13
13
|
|
|
14
|
-
The `
|
|
14
|
+
The `tsci` CLI is interactive by default. You can specify the `-y` or any fully-
|
|
15
15
|
qualified with all required arguments to skip the interactive mode.
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
18
|
# Interactively choose a command and options:
|
|
19
|
-
|
|
19
|
+
tsci
|
|
20
20
|
|
|
21
21
|
# Login
|
|
22
|
-
|
|
22
|
+
tsci login
|
|
23
23
|
|
|
24
24
|
# Create a Project
|
|
25
|
-
|
|
25
|
+
tsci init
|
|
26
26
|
|
|
27
|
-
# Develop a Project
|
|
28
|
-
|
|
27
|
+
# Develop a Project (preview, export, and edit circuit files in browser)
|
|
28
|
+
tsci dev
|
|
29
29
|
|
|
30
30
|
# Manage Dependencies
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
tsci install
|
|
32
|
+
tsci add some-package
|
|
33
|
+
tsci remove some-package
|
|
34
34
|
|
|
35
|
-
#
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
# Publish a Project
|
|
36
|
+
tsci publish
|
|
37
|
+
```
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
tsck format
|
|
41
|
-
tsck format 2024
|
|
39
|
+
## Developing
|
|
42
40
|
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
This project is developed with [bun](https://bun.sh/), make sure you have
|
|
42
|
+
that installed.
|
|
45
43
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
44
|
+
Run `bun boostrap` to install dependencies and `bun cli.ts` to run test the cli in development.
|
|
45
|
+
|
|
46
|
+
To run tests, run `bun test`
|
|
47
|
+
|
|
48
|
+
When you're developing the dev-server, you should do the following:
|
|
49
|
+
|
|
50
|
+
1. Run the development server with `bun start:dev-server:dev`
|
|
51
|
+
2. Upload examples using `bun dev-server upload --watch ./tests/assets/example-project`
|
|
52
|
+
3. Visit `http://localhost:3020` in your browser
|
|
53
|
+
|
|
54
|
+
## Features Coming Soon
|
|
55
|
+
|
|
56
|
+
- [`tsci format`](https://github.com/tscircuit/cli/issues/1)
|
|
57
|
+
- [`tsci lint`](https://github.com/tscircuit/cli/issues/2)
|
|
58
|
+
- [`tsci open`](https://github.com/tscircuit/cli/issues/4)
|
package/bun.lockb
CHANGED
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import kleur from "kleur"
|
|
2
|
+
import { AppContext } from "lib/util/app-context"
|
|
3
|
+
import { z } from "zod"
|
|
4
|
+
import { createOrModifyNpmrc } from "./init/create-or-modify-npmrc"
|
|
5
|
+
import $ from "dax-sh"
|
|
6
|
+
|
|
7
|
+
export const addCmd = async (ctx: AppContext, args: any) => {
|
|
8
|
+
const params = z
|
|
9
|
+
.object({
|
|
10
|
+
packages: z.array(z.string()),
|
|
11
|
+
flags: z
|
|
12
|
+
.object({ dev: z.boolean().optional().default(false) })
|
|
13
|
+
.optional()
|
|
14
|
+
.default({}),
|
|
15
|
+
})
|
|
16
|
+
.parse(args)
|
|
17
|
+
|
|
18
|
+
params.packages = params.packages.map((p) => p.replace(/^@/, ""))
|
|
19
|
+
|
|
20
|
+
await createOrModifyNpmrc({ quiet: true }, ctx)
|
|
21
|
+
|
|
22
|
+
$.cd(ctx.cwd)
|
|
23
|
+
|
|
24
|
+
const flagsString = params.flags.dev ? "--dev" : ""
|
|
25
|
+
|
|
26
|
+
const cmd = `npm add ${flagsString} ${params.packages
|
|
27
|
+
.map((p) => `@tsci/${p.replace(/\//, ".")}`)
|
|
28
|
+
.join(" ")}`
|
|
29
|
+
console.log(kleur.gray(`> ${cmd}`))
|
|
30
|
+
|
|
31
|
+
await $`npm add ${flagsString} ${params.packages
|
|
32
|
+
.map((p) => `@tsci/${p.replace(/\//, ".")}`)
|
|
33
|
+
.join(" ")}`
|
|
34
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { AppContext } from "../../util/app-context"
|
|
2
|
+
import kleur from "kleur"
|
|
3
|
+
import * as Path from "path"
|
|
4
|
+
import { existsSync, readFileSync } from "fs"
|
|
5
|
+
|
|
6
|
+
export const checkIfInitialized = async (ctx: AppContext) => {
|
|
7
|
+
const packageJsonPath = Path.join(ctx.cwd, "package.json")
|
|
8
|
+
if (!existsSync(packageJsonPath)) {
|
|
9
|
+
console.error(kleur.red(`No package.json found`))
|
|
10
|
+
return false
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const packageJsonRaw = readFileSync(packageJsonPath, "utf-8")
|
|
14
|
+
if (!packageJsonRaw.includes("tscircuit")) {
|
|
15
|
+
console.error(
|
|
16
|
+
kleur.red(`No tscircuit dependencies are installed in this project.`)
|
|
17
|
+
)
|
|
18
|
+
return false
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return true
|
|
22
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import apiServer from "../../../dev-server-api/dist/bundle"
|
|
2
|
+
import frontendVfs from "../../../dev-server-frontend/dist/bundle"
|
|
3
|
+
import EdgeRuntimePrimitives from "@edge-runtime/primitives"
|
|
4
|
+
import mime from "mime-types"
|
|
5
|
+
|
|
6
|
+
export const devServerRequestHandler = async (bunReq: Request) => {
|
|
7
|
+
const url = new URL(bunReq.url)
|
|
8
|
+
const requestType = url.pathname.startsWith("/api")
|
|
9
|
+
? "api"
|
|
10
|
+
: url.pathname.startsWith("/preview")
|
|
11
|
+
? "preview"
|
|
12
|
+
: "other"
|
|
13
|
+
|
|
14
|
+
if (requestType === "api") {
|
|
15
|
+
// We have to shim Bun's Request until they fix the issue where
|
|
16
|
+
// .clone() doesn't clone the body
|
|
17
|
+
// https://github.com/oven-sh/bun/pull/8668
|
|
18
|
+
const req = new EdgeRuntimePrimitives.Request(bunReq.url, {
|
|
19
|
+
headers: bunReq.headers,
|
|
20
|
+
method: bunReq.method,
|
|
21
|
+
body: bunReq.body,
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
const response = await apiServer.makeRequest(req, {})
|
|
25
|
+
return response
|
|
26
|
+
} else if (requestType === "preview") {
|
|
27
|
+
let frontendPath = url.pathname.replace("/preview", "")
|
|
28
|
+
if (frontendPath === "/" || frontendPath === "") {
|
|
29
|
+
frontendPath = "index.html"
|
|
30
|
+
}
|
|
31
|
+
frontendPath = frontendPath.replace(/^\//, "")
|
|
32
|
+
|
|
33
|
+
const fileContent: Buffer = (frontendVfs as any)[frontendPath]
|
|
34
|
+
if (!fileContent) {
|
|
35
|
+
return new Response("Not Found", { status: 404 })
|
|
36
|
+
}
|
|
37
|
+
return new Response(
|
|
38
|
+
// Buffer.from(fileContent.toString(), "base64").toString("utf-8"),
|
|
39
|
+
fileContent.toString("utf-8"),
|
|
40
|
+
{
|
|
41
|
+
headers: {
|
|
42
|
+
"Content-Type": mime.lookup(frontendPath) || "text/plain",
|
|
43
|
+
},
|
|
44
|
+
}
|
|
45
|
+
)
|
|
46
|
+
} else {
|
|
47
|
+
return new Response(null, {
|
|
48
|
+
status: 302,
|
|
49
|
+
headers: {
|
|
50
|
+
Location: "/preview",
|
|
51
|
+
},
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import kleur from "kleur"
|
|
2
|
+
import defaultAxios from "axios"
|
|
3
|
+
|
|
4
|
+
export const getDevServerAxios = ({ serverUrl }: { serverUrl: string }) => {
|
|
5
|
+
const devServerAxios = defaultAxios.create({
|
|
6
|
+
baseURL: serverUrl,
|
|
7
|
+
})
|
|
8
|
+
devServerAxios.interceptors.response.use(
|
|
9
|
+
(res) => res,
|
|
10
|
+
(err) => {
|
|
11
|
+
console.log(
|
|
12
|
+
kleur.red(
|
|
13
|
+
`[ERR] ${err.response?.status} ${err.config.method?.toUpperCase()} ${
|
|
14
|
+
err.config.url
|
|
15
|
+
}\n\n${JSON.stringify(err.response?.data, null, " ")}`
|
|
16
|
+
.replace(/\\n/g, "\n")
|
|
17
|
+
.replace(/\\"/g, '"')
|
|
18
|
+
)
|
|
19
|
+
)
|
|
20
|
+
console.log(kleur.yellow("[Request Body]:"), err.config.data)
|
|
21
|
+
return Promise.reject(err)
|
|
22
|
+
}
|
|
23
|
+
)
|
|
24
|
+
return devServerAxios
|
|
25
|
+
}
|
package/lib/cmd-fns/dev/index.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { AppContext } from "../../util/app-context"
|
|
2
2
|
import { z } from "zod"
|
|
3
3
|
import kleur from "kleur"
|
|
4
|
-
import { join as joinPath } from "path"
|
|
5
4
|
import prompts from "prompts"
|
|
6
5
|
import open from "open"
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import {
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
import
|
|
6
|
+
import { startDevServer } from "./start-dev-server"
|
|
7
|
+
import { getDevServerAxios } from "./get-dev-server-axios"
|
|
8
|
+
import { uploadExamplesFromDirectory } from "./upload-examples-from-directory"
|
|
9
|
+
import { unlink } from "fs/promises"
|
|
10
|
+
import * as Path from "path"
|
|
11
|
+
import { startWatcher } from "./start-watcher"
|
|
12
|
+
import { createOrModifyNpmrc } from "../init/create-or-modify-npmrc"
|
|
13
|
+
import { checkIfInitialized } from "./check-if-initialized"
|
|
14
14
|
|
|
15
15
|
export const devCmd = async (ctx: AppContext, args: any) => {
|
|
16
16
|
const params = z
|
|
@@ -22,28 +22,32 @@ export const devCmd = async (ctx: AppContext, args: any) => {
|
|
|
22
22
|
|
|
23
23
|
const { cwd, port } = params
|
|
24
24
|
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
|
|
28
|
-
// TODO
|
|
25
|
+
// In the future we should automatically run "tsci init" if the directory
|
|
26
|
+
// isn't properly initialized, for now we're just going to do a spot check
|
|
27
|
+
const isInitialized = await checkIfInitialized(ctx)
|
|
29
28
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
if (!isInitialized) {
|
|
30
|
+
console.log(
|
|
31
|
+
kleur.red(
|
|
32
|
+
`This project is not properly initialized. Please run "tsci init" first, or follow the manual installation steps here:\n\nhttps://github.com/tscircuit/tscircuit/blob/main/docs/manual-installation.md`
|
|
33
|
+
)
|
|
34
|
+
)
|
|
35
|
+
process.exit(1)
|
|
36
|
+
}
|
|
33
37
|
|
|
34
|
-
|
|
38
|
+
await createOrModifyNpmrc({ quiet: false }, ctx)
|
|
39
|
+
|
|
40
|
+
// Load package.json, install tscircuit if it's not installed. If any other
|
|
41
|
+
// tscircuit dependency like @tscircuit/builder is installed, then don't
|
|
42
|
+
// install anything. If there is nothing in the current directory, ask to
|
|
43
|
+
// instead run "tsci init"
|
|
35
44
|
// TODO
|
|
36
45
|
|
|
37
46
|
// Add .tscircuit to .gitignore if it's not already there
|
|
38
47
|
// TODO
|
|
39
48
|
|
|
40
|
-
// Delete
|
|
41
|
-
|
|
42
|
-
// INSIDE BACKGROUND SERVICE
|
|
43
|
-
// Soupify contents of examples directory, upload to sqlite db
|
|
44
|
-
// TODO
|
|
45
|
-
// Watch and re-soupify on file changes
|
|
46
|
-
// TODO
|
|
49
|
+
// Delete old .tscircuit/dev-server.db
|
|
50
|
+
unlink(Path.join(cwd, ".tscircuit/dev-server.db")).catch(() => {})
|
|
47
51
|
|
|
48
52
|
console.log(
|
|
49
53
|
kleur.green(
|
|
@@ -51,144 +55,16 @@ export const devCmd = async (ctx: AppContext, args: any) => {
|
|
|
51
55
|
)
|
|
52
56
|
)
|
|
53
57
|
const serverUrl = `http://localhost:${port}`
|
|
54
|
-
const
|
|
55
|
-
baseURL: serverUrl,
|
|
56
|
-
})
|
|
57
|
-
axios.interceptors.response.use(
|
|
58
|
-
(res) => res,
|
|
59
|
-
(err) => {
|
|
60
|
-
console.log(
|
|
61
|
-
kleur.red(
|
|
62
|
-
`[ERR] ${err.response?.status} ${err.config.method?.toUpperCase()} ${
|
|
63
|
-
err.config.url
|
|
64
|
-
}\n\n${JSON.stringify(err.response?.data, null, " ")}`
|
|
65
|
-
.replace(/\\n/g, "\n")
|
|
66
|
-
.replace(/\\"/g, '"')
|
|
67
|
-
)
|
|
68
|
-
)
|
|
69
|
-
console.log(kleur.yellow("[Request Body]:"), err.config.data)
|
|
70
|
-
return Promise.reject(err)
|
|
71
|
-
}
|
|
72
|
-
)
|
|
73
|
-
|
|
74
|
-
const requestHandler = async (bunReq: Request) => {
|
|
75
|
-
const url = new URL(bunReq.url)
|
|
76
|
-
const requestType = url.pathname.startsWith("/api")
|
|
77
|
-
? "api"
|
|
78
|
-
: url.pathname.startsWith("/preview")
|
|
79
|
-
? "preview"
|
|
80
|
-
: "other"
|
|
58
|
+
const devServerAxios = getDevServerAxios({ serverUrl })
|
|
81
59
|
|
|
82
|
-
|
|
83
|
-
// We have to shim Bun's Request until they fix the issue where
|
|
84
|
-
// .clone() doesn't clone the body
|
|
85
|
-
// https://github.com/oven-sh/bun/pull/8668
|
|
86
|
-
const req = new EdgeRuntimePrimitives.Request(bunReq.url, {
|
|
87
|
-
headers: bunReq.headers,
|
|
88
|
-
method: bunReq.method,
|
|
89
|
-
body: bunReq.body,
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
const response = await apiServer.makeRequest(req, {})
|
|
93
|
-
return response
|
|
94
|
-
} else if (requestType === "preview") {
|
|
95
|
-
let frontendPath = url.pathname.replace("/preview", "")
|
|
96
|
-
if (frontendPath === "/" || frontendPath === "") {
|
|
97
|
-
frontendPath = "index.html"
|
|
98
|
-
}
|
|
99
|
-
frontendPath = frontendPath.replace(/^\//, "")
|
|
100
|
-
|
|
101
|
-
const fileContent: Buffer = (frontendVfs as any)[frontendPath]
|
|
102
|
-
if (!fileContent) {
|
|
103
|
-
return new Response("Not Found", { status: 404 })
|
|
104
|
-
}
|
|
105
|
-
return new Response(
|
|
106
|
-
// Buffer.from(fileContent.toString(), "base64").toString("utf-8"),
|
|
107
|
-
fileContent.toString("utf-8"),
|
|
108
|
-
{
|
|
109
|
-
headers: {
|
|
110
|
-
"Content-Type": mime.lookup(frontendPath) || "text/plain",
|
|
111
|
-
},
|
|
112
|
-
}
|
|
113
|
-
)
|
|
114
|
-
} else {
|
|
115
|
-
return new Response(null, {
|
|
116
|
-
status: 302,
|
|
117
|
-
headers: {
|
|
118
|
-
Location: "/preview",
|
|
119
|
-
},
|
|
120
|
-
})
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
let server: any
|
|
125
|
-
if (typeof Bun !== "undefined") {
|
|
126
|
-
server = Bun.serve({
|
|
127
|
-
fetch: requestHandler,
|
|
128
|
-
development: false,
|
|
129
|
-
port,
|
|
130
|
-
})
|
|
131
|
-
} else {
|
|
132
|
-
// Hono messes up the globals, only import it if we don't have Bun
|
|
133
|
-
const { Hono } = await import("hono")
|
|
134
|
-
const { serve } = await import("@hono/node-server")
|
|
135
|
-
const honoApp = new Hono()
|
|
136
|
-
honoApp.all("/*", (c) => requestHandler(c.req.raw))
|
|
137
|
-
server = serve({
|
|
138
|
-
fetch: honoApp.fetch,
|
|
139
|
-
port,
|
|
140
|
-
})
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
console.log("Running health check against dev server...")
|
|
144
|
-
await axios.get("/api/health")
|
|
60
|
+
const server = await startDevServer({ port, devServerAxios })
|
|
145
61
|
|
|
146
62
|
// Soupify all examples
|
|
147
63
|
console.log(`Loading examples...`)
|
|
148
|
-
|
|
149
|
-
const exampleFileNames = readdirSync(examplesDir)
|
|
150
|
-
for (const exampleFileName of exampleFileNames) {
|
|
151
|
-
try {
|
|
152
|
-
const examplePath = joinPath(examplesDir, exampleFileName)
|
|
153
|
-
const exampleContent = readFileSync(examplePath).toString()
|
|
64
|
+
await uploadExamplesFromDirectory({ devServerAxios, cwd })
|
|
154
65
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
if (!exampleContent.includes("export default")) {
|
|
158
|
-
// try to infer an export if possible
|
|
159
|
-
const matches = Array.from(
|
|
160
|
-
exampleContent.matchAll(
|
|
161
|
-
/export\s+(const|function)\s+([A-Z]\w+)\s*=?/g
|
|
162
|
-
)
|
|
163
|
-
).map((m) => m[2])
|
|
164
|
-
if (matches.length === 0) {
|
|
165
|
-
throw new Error(`No export detected in "${exampleFileName}"`)
|
|
166
|
-
}
|
|
167
|
-
if (matches.length > 1) {
|
|
168
|
-
throw new Error(
|
|
169
|
-
`Multiple exports detected in "${exampleFileName}", only single exports currently working`
|
|
170
|
-
)
|
|
171
|
-
}
|
|
172
|
-
exportName = matches[0]
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
console.log("Soupifying...")
|
|
176
|
-
const soup = await soupify({
|
|
177
|
-
filePath: examplePath,
|
|
178
|
-
exportName,
|
|
179
|
-
})
|
|
180
|
-
console.log(`Soupified ${exampleFileName}!`)
|
|
181
|
-
console.log(`Uploading ${exampleFileName}...`)
|
|
182
|
-
await axios.post("/api/dev_package_examples/create", {
|
|
183
|
-
tscircuit_soup: soup,
|
|
184
|
-
file_path: examplePath,
|
|
185
|
-
export_name: exportName,
|
|
186
|
-
})
|
|
187
|
-
console.log(`Uploaded ${exampleFileName}!`)
|
|
188
|
-
} catch (e: any) {
|
|
189
|
-
console.log(kleur.red(e.toString()))
|
|
190
|
-
}
|
|
191
|
-
}
|
|
66
|
+
// Start watcher
|
|
67
|
+
const watcher = await startWatcher({ cwd, devServerAxios })
|
|
192
68
|
|
|
193
69
|
while (true) {
|
|
194
70
|
const { action } = await prompts({
|
|
@@ -211,6 +87,7 @@ export const devCmd = async (ctx: AppContext, args: any) => {
|
|
|
211
87
|
} else if (!action || action === "stop") {
|
|
212
88
|
if (server.stop) server.stop()
|
|
213
89
|
if (server.close) server.close()
|
|
90
|
+
watcher.stop()
|
|
214
91
|
break
|
|
215
92
|
}
|
|
216
93
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export const inferExportNameFromSource = (sourceContent: string): string => {
|
|
2
|
+
if (sourceContent.includes("export default")) {
|
|
3
|
+
return "default"
|
|
4
|
+
}
|
|
5
|
+
const matches = Array.from(
|
|
6
|
+
sourceContent.matchAll(/export\s+(const|function)\s+([A-Z]\w+)\s*=?/g)
|
|
7
|
+
).map((m) => m[2])
|
|
8
|
+
if (matches.length === 0) {
|
|
9
|
+
throw new Error(`No export detected in "${sourceContent}"`)
|
|
10
|
+
}
|
|
11
|
+
if (matches.length > 1) {
|
|
12
|
+
throw new Error(
|
|
13
|
+
`Multiple exports detected in "${sourceContent}", only single exports currently working`
|
|
14
|
+
)
|
|
15
|
+
}
|
|
16
|
+
return matches[0]
|
|
17
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import kleur from "kleur"
|
|
2
|
+
import { join as joinPath } from "path"
|
|
3
|
+
import { AxiosInstance } from "axios"
|
|
4
|
+
import { readdirSync, readFileSync } from "fs"
|
|
5
|
+
import { soupify } from "lib/soupify"
|
|
6
|
+
import { inferExportNameFromSource } from "./infer-export-name-from-source"
|
|
7
|
+
|
|
8
|
+
export const soupifyAndUploadExampleFile = async ({
|
|
9
|
+
examplesDir,
|
|
10
|
+
exampleFileName,
|
|
11
|
+
devServerAxios,
|
|
12
|
+
}: {
|
|
13
|
+
examplesDir: string
|
|
14
|
+
exampleFileName: string
|
|
15
|
+
devServerAxios: AxiosInstance
|
|
16
|
+
}) => {
|
|
17
|
+
try {
|
|
18
|
+
const examplePath = joinPath(examplesDir, exampleFileName)
|
|
19
|
+
const exampleContent = readFileSync(examplePath).toString()
|
|
20
|
+
|
|
21
|
+
const exportName = inferExportNameFromSource(exampleContent)
|
|
22
|
+
|
|
23
|
+
console.log(kleur.gray(`[soupifying] ${exampleFileName}...`))
|
|
24
|
+
const soup = await soupify({
|
|
25
|
+
filePath: examplePath,
|
|
26
|
+
exportName,
|
|
27
|
+
})
|
|
28
|
+
console.log(kleur.gray(`[uploading ] ${exampleFileName}...`))
|
|
29
|
+
await devServerAxios.post("/api/dev_package_examples/create", {
|
|
30
|
+
tscircuit_soup: soup,
|
|
31
|
+
file_path: examplePath,
|
|
32
|
+
export_name: exportName,
|
|
33
|
+
})
|
|
34
|
+
console.log(kleur.gray(`[ done ] ${exampleFileName}!`))
|
|
35
|
+
} catch (e: any) {
|
|
36
|
+
console.log(kleur.red(e.toString()))
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { AxiosInstance } from "axios"
|
|
2
|
+
import { devServerRequestHandler } from "./dev-server-request-handler"
|
|
3
|
+
|
|
4
|
+
export const startDevServer = async ({
|
|
5
|
+
port,
|
|
6
|
+
devServerAxios,
|
|
7
|
+
}: {
|
|
8
|
+
port: number
|
|
9
|
+
devServerAxios: AxiosInstance
|
|
10
|
+
}) => {
|
|
11
|
+
let server: any
|
|
12
|
+
if (typeof Bun !== "undefined") {
|
|
13
|
+
server = Bun.serve({
|
|
14
|
+
fetch: devServerRequestHandler,
|
|
15
|
+
development: false,
|
|
16
|
+
port,
|
|
17
|
+
})
|
|
18
|
+
} else {
|
|
19
|
+
// Hono messes up the globals, only import it if we don't have Bun
|
|
20
|
+
const { Hono } = await import("hono")
|
|
21
|
+
const { serve } = await import("@hono/node-server")
|
|
22
|
+
const honoApp = new Hono()
|
|
23
|
+
honoApp.all("/*", (c) => devServerRequestHandler(c.req.raw))
|
|
24
|
+
server = serve({
|
|
25
|
+
fetch: honoApp.fetch,
|
|
26
|
+
port,
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
console.log("Running health check against dev server...")
|
|
31
|
+
await devServerAxios.get("/api/health")
|
|
32
|
+
|
|
33
|
+
return server
|
|
34
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { AxiosInstance } from "axios"
|
|
2
|
+
import chokidar from "chokidar"
|
|
3
|
+
import { uploadExamplesFromDirectory } from "./upload-examples-from-directory"
|
|
4
|
+
import kleur from "kleur"
|
|
5
|
+
|
|
6
|
+
export const startWatcher = async ({
|
|
7
|
+
cwd,
|
|
8
|
+
devServerAxios,
|
|
9
|
+
}: {
|
|
10
|
+
cwd: string
|
|
11
|
+
devServerAxios: AxiosInstance
|
|
12
|
+
}) => {
|
|
13
|
+
const watcher = chokidar.watch(`${cwd}/**/*.tsx`, {
|
|
14
|
+
ignored: /node_modules/,
|
|
15
|
+
persistent: true,
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
const upload_queue_state = {
|
|
19
|
+
dirty: false,
|
|
20
|
+
should_run: true,
|
|
21
|
+
}
|
|
22
|
+
watcher.on("change", async (path) => {
|
|
23
|
+
console.log(`File ${path} has been changed`)
|
|
24
|
+
// TODO analyze to determine which examples were impacted
|
|
25
|
+
upload_queue_state.dirty = true
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
async function uploadInBackground() {
|
|
29
|
+
while (upload_queue_state.should_run) {
|
|
30
|
+
if (upload_queue_state.dirty) {
|
|
31
|
+
console.log(kleur.yellow("Changes detected, re-uploading examples..."))
|
|
32
|
+
upload_queue_state.dirty = false
|
|
33
|
+
await uploadExamplesFromDirectory({ cwd, devServerAxios })
|
|
34
|
+
}
|
|
35
|
+
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const _backgroundUploaderPromise = uploadInBackground()
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
_backgroundUploaderPromise,
|
|
43
|
+
stop: () => {
|
|
44
|
+
upload_queue_state.should_run = false
|
|
45
|
+
watcher.close()
|
|
46
|
+
},
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import kleur from "kleur"
|
|
2
|
+
import { join as joinPath } from "path"
|
|
3
|
+
import { AxiosInstance } from "axios"
|
|
4
|
+
import { readdirSync, readFileSync } from "fs"
|
|
5
|
+
import { soupify } from "lib/soupify"
|
|
6
|
+
import { soupifyAndUploadExampleFile } from "./soupify-and-upload-example-file"
|
|
7
|
+
|
|
8
|
+
export const uploadExamplesFromDirectory = async ({
|
|
9
|
+
cwd,
|
|
10
|
+
devServerAxios,
|
|
11
|
+
}: {
|
|
12
|
+
cwd: string
|
|
13
|
+
devServerAxios: AxiosInstance
|
|
14
|
+
}) => {
|
|
15
|
+
const examplesDir = joinPath(cwd, "examples")
|
|
16
|
+
const exampleFileNames = readdirSync(examplesDir)
|
|
17
|
+
for (const exampleFileName of exampleFileNames) {
|
|
18
|
+
if (exampleFileName.endsWith(".__tmp_entrypoint.tsx")) continue
|
|
19
|
+
if (!exampleFileName.endsWith(".tsx")) continue
|
|
20
|
+
await soupifyAndUploadExampleFile({
|
|
21
|
+
devServerAxios,
|
|
22
|
+
examplesDir,
|
|
23
|
+
exampleFileName,
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { AppContext } from "../util/app-context"
|
|
2
|
+
import { z } from "zod"
|
|
3
|
+
import { getDevServerAxios } from "./get-dev-server-axios"
|
|
4
|
+
import { uploadExamplesFromDirectory } from "./upload-examples-from-directory"
|
|
5
|
+
import { startWatcher } from "./dev/start-watcher"
|
|
6
|
+
|
|
7
|
+
export const devServerUpload = async (ctx: AppContext, args: any) => {
|
|
8
|
+
const params = z
|
|
9
|
+
.object({
|
|
10
|
+
dir: z.string().optional().default(ctx.cwd),
|
|
11
|
+
port: z.coerce.number().optional().default(3020),
|
|
12
|
+
watch: z.boolean().optional().default(false),
|
|
13
|
+
})
|
|
14
|
+
.parse(args)
|
|
15
|
+
|
|
16
|
+
const serverUrl = `http://localhost:${params.port}`
|
|
17
|
+
const devServerAxios = getDevServerAxios({ serverUrl })
|
|
18
|
+
|
|
19
|
+
console.log(`Loading examples...`)
|
|
20
|
+
await uploadExamplesFromDirectory({ devServerAxios, cwd: params.dir })
|
|
21
|
+
|
|
22
|
+
if (params.watch) {
|
|
23
|
+
// Start watcher
|
|
24
|
+
const watcher = await startWatcher({ cwd: params.dir, devServerAxios })
|
|
25
|
+
}
|
|
26
|
+
}
|
package/lib/cmd-fns/index.ts
CHANGED
|
@@ -26,3 +26,8 @@ export { publish } from "./publish"
|
|
|
26
26
|
export { soupifyCmd as soupify } from "./soupify"
|
|
27
27
|
export { devCmd as dev } from "./dev"
|
|
28
28
|
export { initCmd as init } from "./init"
|
|
29
|
+
export { addCmd as add } from "./add"
|
|
30
|
+
export { removeCmd as remove } from "./remove"
|
|
31
|
+
export { installCmd as install } from "./install"
|
|
32
|
+
export { uninstallCmd as uninstall } from "./uninstall"
|
|
33
|
+
export { devServerUpload } from "./dev-server-upload"
|