@tscircuit/cli 0.1.22 → 0.1.24

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.
@@ -104,7 +104,7 @@ export default () => {
104
104
  ySpacing: 5,
105
105
  offsetX: 3 - 122,
106
106
  offsetY: -32 / 2 - 7.5,
107
- }).map(({ center, index, row, col }) => {
107
+ }).map(({ center, index }) => {
108
108
  const ledName = `LED${index + 1}`
109
109
  const prevLedName = index > 0 ? `LED${index}` : null
110
110
  const capName = `C_${ledName}`
@@ -0,0 +1,17 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES6",
4
+ "module": "ESNext",
5
+ "jsx": "react-jsx",
6
+ "outDir": "dist",
7
+ "strict": true,
8
+ "esModuleInterop": true,
9
+ "moduleResolution": "node",
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "resolveJsonModule": true,
13
+ "sourceMap": true,
14
+ "allowSyntheticDefaultImports": true,
15
+ "experimentalDecorators": true
16
+ }
17
+ }
@@ -9,3 +9,23 @@ declare module "@tsci/seveibar.push-button" {
9
9
  declare module "@tsci/seveibar.smd-usb-c" {
10
10
  export function useUsbC(name: string): any
11
11
  }
12
+
13
+ declare module "@tsci/seveibar.PICO_W" {
14
+ export function usePICO_W(name: string): any
15
+ }
16
+
17
+ declare module "@tsci/seveibar.HS91L02W2C01" {
18
+ export function useHS91L02W2C01(name: string): any
19
+ }
20
+
21
+ declare module "@tsci/seveibar.WS2812B_2020" {
22
+ interface WS2812B_2020Props {
23
+ schX: number
24
+ schY: number
25
+ name: string
26
+ pcbX: number
27
+ pcbY: number
28
+ }
29
+
30
+ export const WS2812B_2020: React.FC<WS2812B_2020Props>
31
+ }
@@ -1,4 +1,6 @@
1
- export interface FileServerRoutes {
1
+ import { EventsRoutes } from "lib/server/EventsRoutes"
2
+
3
+ export interface FileServerRoutes extends EventsRoutes {
2
4
  "api/files/get": {
3
5
  GET: {
4
6
  searchParams: {
@@ -0,0 +1,32 @@
1
+ export interface EventsRoutes {
2
+ "api/events/create": {
3
+ POST: {
4
+ requestJson: {
5
+ event_type: string
6
+ }
7
+ responseJson: {
8
+ event: {
9
+ event_id: string
10
+ event_type: string
11
+ }
12
+ }
13
+ }
14
+ }
15
+ "api/events/list": {
16
+ GET: {
17
+ responseJson: {
18
+ event_list: Array<{
19
+ event_id: string
20
+ event_type:
21
+ | "FILE_UPDATED"
22
+ | "FAILED_TO_SAVE_SNIPPET"
23
+ | "SNIPPET_SAVED"
24
+ | "REQUEST_TO_SAVE_SNIPPET"
25
+ file_path: string
26
+ created_at: string
27
+ initiator?: string
28
+ }>
29
+ }
30
+ }
31
+ }
32
+ }
@@ -0,0 +1,198 @@
1
+ import { cliConfig } from "lib/cli-config"
2
+ import { getKy } from "lib/registry-api/get-ky"
3
+ import * as fs from "node:fs"
4
+ import * as path from "node:path"
5
+ import semver from "semver"
6
+
7
+ type PushOptions = {
8
+ filePath?: string
9
+ onExit?: (code: number) => void
10
+ onError?: (message: string) => void
11
+ onSuccess?: (message: string) => void
12
+ }
13
+
14
+ export const pushSnippet = async ({
15
+ filePath,
16
+ onExit = (code) => process.exit(code),
17
+ onError = (message) => console.error(message),
18
+ onSuccess = (message) => console.log(message),
19
+ }: PushOptions) => {
20
+ const sessionToken = cliConfig.get("sessionToken")
21
+ if (!sessionToken) {
22
+ onError("You need to log in to save snippet.")
23
+ return onExit(1)
24
+ }
25
+
26
+ let snippetFilePath: string | null = null
27
+ if (filePath) {
28
+ snippetFilePath = path.resolve(filePath)
29
+ } else {
30
+ const defaultEntrypoint = path.resolve("index.tsx")
31
+ if (fs.existsSync(defaultEntrypoint)) {
32
+ snippetFilePath = defaultEntrypoint
33
+ onSuccess("No file provided. Using 'index.tsx' as the entrypoint.")
34
+ } else {
35
+ onError(
36
+ "No entrypoint found. Run 'tsci init' to bootstrap a basic project.",
37
+ )
38
+ return onExit(1)
39
+ }
40
+ }
41
+
42
+ const packageJsonPath = path.resolve(
43
+ path.join(path.dirname(snippetFilePath), "package.json"),
44
+ )
45
+ let packageJson: { name?: string; author?: string; version?: string } = {}
46
+ if (fs.existsSync(packageJsonPath)) {
47
+ try {
48
+ packageJson = JSON.parse(fs.readFileSync(packageJsonPath).toString())
49
+ } catch {
50
+ onError("Invalid package.json provided")
51
+ return onExit(1)
52
+ }
53
+ }
54
+
55
+ if (!fs.existsSync(snippetFilePath)) {
56
+ onError(`File not found: ${snippetFilePath}`)
57
+ return onExit(1)
58
+ }
59
+
60
+ const ky = getKy()
61
+ const packageName = (
62
+ packageJson.name ?? path.parse(snippetFilePath).name
63
+ ).replace(/^@/, "")
64
+ const packageAuthor =
65
+ packageJson.author?.split(" ")[0] ?? cliConfig.get("githubUsername")
66
+ const packageIdentifier = `${packageAuthor}/${packageName}`
67
+
68
+ let packageVersion =
69
+ packageJson.version ??
70
+ (await ky
71
+ .post<{
72
+ error?: { error_code: string }
73
+ package_releases?: { version: string; is_latest: boolean }[]
74
+ }>("package_releases/list", {
75
+ json: { package_name: packageIdentifier },
76
+ })
77
+ .json()
78
+ .then(
79
+ (response) =>
80
+ response.package_releases?.[response.package_releases.length - 1]
81
+ ?.version,
82
+ )
83
+ .catch((error) => {
84
+ onError("Failed to retrieve latest package version:" + error)
85
+ return onExit(1)
86
+ }))
87
+
88
+ if (!packageVersion) {
89
+ onError("Failed to retrieve package version.")
90
+ return onExit(1)
91
+ }
92
+
93
+ const updatePackageJsonVersion = (newVersion?: string) => {
94
+ if (packageJson.version) {
95
+ try {
96
+ packageJson.version = newVersion ?? `${packageVersion}`
97
+ fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2))
98
+ } catch (error) {
99
+ onError("Failed to update package.json version:" + error)
100
+ }
101
+ }
102
+ }
103
+
104
+ const doesPackageExist = await ky
105
+ .post<{ error?: { error_code: string } }>("packages/get", {
106
+ json: { name: packageIdentifier },
107
+ throwHttpErrors: false,
108
+ })
109
+ .json()
110
+ .then((response) => !(response.error?.error_code === "package_not_found"))
111
+
112
+ if (!doesPackageExist) {
113
+ await ky
114
+ .post("packages/create", {
115
+ json: { name: packageIdentifier },
116
+ headers: { Authorization: `Bearer ${sessionToken}` },
117
+ })
118
+ .catch((error) => {
119
+ onError("Error creating package:" + error)
120
+ return onExit(1)
121
+ })
122
+ }
123
+
124
+ const doesReleaseExist = await ky
125
+ .post<{
126
+ error?: { error_code: string }
127
+ package_release?: { version: string }
128
+ }>("package_releases/get", {
129
+ json: {
130
+ package_name_with_version: `${packageIdentifier}@${packageVersion}`,
131
+ },
132
+ throwHttpErrors: false,
133
+ })
134
+ .json()
135
+ .then((response) => {
136
+ if (response.package_release?.version) {
137
+ packageVersion = response.package_release.version
138
+ updatePackageJsonVersion(response.package_release.version)
139
+ return true
140
+ }
141
+ return !(response.error?.error_code === "package_release_not_found")
142
+ })
143
+
144
+ if (doesReleaseExist) {
145
+ const bumpedVersion = semver.inc(packageVersion, "patch")!
146
+ onSuccess(
147
+ `Incrementing Package Version ${packageVersion} -> ${bumpedVersion}`,
148
+ )
149
+ packageVersion = bumpedVersion
150
+ updatePackageJsonVersion(packageVersion)
151
+ }
152
+
153
+ await ky
154
+ .post("package_releases/create", {
155
+ json: {
156
+ package_name_with_version: `${packageIdentifier}@${packageVersion}`,
157
+ },
158
+ throwHttpErrors: false,
159
+ })
160
+ .catch((error) => {
161
+ onError("Error creating release:" + error)
162
+ return onExit(1)
163
+ })
164
+
165
+ onSuccess("\n")
166
+
167
+ const directoryFiles = fs.readdirSync(path.dirname(snippetFilePath))
168
+ for (const file of directoryFiles) {
169
+ const fileExtension = path.extname(file).replace(".", "")
170
+ if (!["json", "tsx", "ts"].includes(fileExtension)) continue
171
+ const fileContent =
172
+ fs
173
+ .readFileSync(path.join(path.dirname(snippetFilePath), file))
174
+ .toString() ?? ""
175
+ await ky
176
+ .post("package_files/create", {
177
+ json: {
178
+ file_path: file,
179
+ content_text: fileContent,
180
+ package_name_with_version: `${packageIdentifier}@${packageVersion}`,
181
+ },
182
+ throwHttpErrors: false,
183
+ })
184
+ .then(() => {
185
+ onSuccess(`Uploaded file ${file} to the registry.`)
186
+ })
187
+ .catch((error) => {
188
+ onError(`Error uploading file ${file}:` + error)
189
+ })
190
+ }
191
+
192
+ onSuccess(
193
+ [
194
+ `\n🎉 Successfully pushed package ${packageIdentifier}@${packageVersion} to the registry!${Bun.color("blue", "ansi")}`,
195
+ `https://tscircuit.com/${packageIdentifier} \x1b[0m`,
196
+ ].join(" "),
197
+ )
198
+ }
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@tscircuit/cli",
3
3
  "main": "dist/main.js",
4
4
  "type": "module",
5
- "version": "0.1.22",
5
+ "version": "0.1.24",
6
6
  "bin": {
7
7
  "tsci": "./dist/main.js"
8
8
  },
@@ -20,7 +20,7 @@
20
20
  "@tscircuit/fake-snippets": "^0.0.5",
21
21
  "@types/bun": "^1.1.15",
22
22
  "@types/configstore": "^6.0.2",
23
- "@types/react": "^19.0.1",
23
+ "@types/react": "^19.0.8",
24
24
  "@types/semver": "^7.5.8",
25
25
  "get-port": "^7.1.0",
26
26
  "tempy": "^3.1.0",
@@ -33,7 +33,7 @@
33
33
  "dependencies": {
34
34
  "@tscircuit/file-server": "^0.0.13",
35
35
  "@tscircuit/runframe": "^0.0.47",
36
- "chokidar": "^4.0.1",
36
+ "chokidar": "4.0.1",
37
37
  "commander": "^12.1.0",
38
38
  "configstore": "^7.0.0",
39
39
  "cosmiconfig": "^9.0.0",
@@ -0,0 +1,34 @@
1
+ import { getCliTestFixture } from "../../fixtures/get-cli-test-fixture"
2
+ import { test, expect } from "bun:test"
3
+ import { join } from "node:path"
4
+ import { execSync } from "node:child_process"
5
+
6
+ test("init command installs @types/react and passes type-checking", async () => {
7
+ const { tmpDir, runCommand } = await getCliTestFixture()
8
+
9
+ const { stdout } = await runCommand("tsci init")
10
+ console.log(stdout)
11
+
12
+ const pkgJsonPath = join(tmpDir, "package.json")
13
+ const pkgJson = JSON.parse(await Bun.file(pkgJsonPath).text())
14
+ expect(pkgJson.devDependencies["@types/react"]).toBeDefined()
15
+ expect(pkgJson.devDependencies["@tscircuit/core"]).toBeDefined()
16
+
17
+ const npmrcPath = join(tmpDir, ".npmrc")
18
+ const npmrcContent = await Bun.file(npmrcPath).text()
19
+ expect(npmrcContent).toContain("@tsci:registry=https://npm.tscircuit.com")
20
+
21
+ const tsconfigPath = join(tmpDir, "tsconfig.json")
22
+ const tsconfigExists = await Bun.file(tsconfigPath).exists()
23
+ expect(tsconfigExists).toBeTrue()
24
+
25
+ try {
26
+ const typeCheckResult = execSync("npx tsc --noEmit", {
27
+ cwd: tmpDir,
28
+ stdio: "pipe",
29
+ })
30
+ console.log(typeCheckResult.toString())
31
+ } catch (error) {
32
+ throw new Error("Type-checking failed")
33
+ }
34
+ })
@@ -0,0 +1,90 @@
1
+ import { test, expect, afterAll } from "bun:test"
2
+ import { DevServer } from "cli/dev/DevServer"
3
+ import { getTestFixture } from "tests/fixtures/get-test-fixture"
4
+ import { EventsWatcher } from "lib/server/EventsWatcher"
5
+ import { getTestSnippetsServer } from "./fixtures/get-test-server"
6
+ import { cliConfig } from "lib/cli-config"
7
+
8
+ test("test saveSnippet via REQUEST_TO_SAVE_SNIPPET event with CLI token setup", async () => {
9
+ afterAll(() => {
10
+ eventManager.stop()
11
+ devServer.stop()
12
+ })
13
+
14
+ // Start snippets server
15
+ await getTestSnippetsServer()
16
+
17
+ // Set up a temporary directory with a sample snippet file
18
+ const { tempDirPath, devServerPort, devServerUrl } = await getTestFixture({
19
+ vfs: {
20
+ "snippet.tsx": `
21
+ export const MyCircuit = () => (
22
+ <board width="10mm" height="10mm">
23
+ <chip name="U1" footprint="soic8" />
24
+ </board>
25
+ )
26
+ `,
27
+ "manual-edits.json": "{}",
28
+ "package.json": JSON.stringify({
29
+ version: "0.0.1",
30
+ name: "snippet",
31
+ author: "test-author",
32
+ }),
33
+ },
34
+ })
35
+
36
+ // Create and start the DevServer instance
37
+ const devServer = new DevServer({
38
+ port: devServerPort,
39
+ componentFilePath: `${tempDirPath}/snippet.tsx`,
40
+ })
41
+ await devServer.start()
42
+
43
+ // Start the EventsWatcher to listen for events
44
+ const eventManager = new EventsWatcher(devServerUrl)
45
+ await eventManager.start()
46
+
47
+ // Emit the REQUEST_TO_SAVE_SNIPPET event
48
+ devServer.fsKy.post("api/events/create", {
49
+ json: { event_type: "REQUEST_TO_SAVE_SNIPPET" },
50
+ })
51
+
52
+ // Promises to wait for specific events using Promise.withResolvers()
53
+ const {
54
+ promise: requestToSaveSnippetPromise,
55
+ resolve: resolveRequestToSaveSnippet,
56
+ } = Promise.withResolvers<void>()
57
+ eventManager.on("REQUEST_TO_SAVE_SNIPPET", async () => {
58
+ resolveRequestToSaveSnippet()
59
+ })
60
+
61
+ const sessionToken = cliConfig.get("sessionToken")
62
+ cliConfig.delete("sessionToken")
63
+
64
+ const { promise: snippetSavedPromise, resolve: resolveSnippetSaved } =
65
+ Promise.withResolvers<void>()
66
+ eventManager.on("SNIPPET_SAVED", () => {
67
+ resolveSnippetSaved()
68
+ })
69
+
70
+ const {
71
+ promise: snippetSaveFailedPromise,
72
+ resolve: resolveSnippetSaveFailed,
73
+ } = Promise.withResolvers<void>()
74
+ eventManager.on("FAILED_TO_SAVE_SNIPPET", () => {
75
+ resolveSnippetSaveFailed()
76
+ cliConfig.set("sessionToken", sessionToken)
77
+ devServer.fsKy.post("api/events/create", {
78
+ json: { event_type: "REQUEST_TO_SAVE_SNIPPET" },
79
+ })
80
+ })
81
+
82
+ // Wait for the REQUEST_TO_SAVE_SNIPPET event to be detected
83
+ expect(requestToSaveSnippetPromise).resolves.toBeUndefined()
84
+
85
+ // Wait for the FAILED_TO_SAVE_SNIPPET event to be detected
86
+ expect(snippetSaveFailedPromise).resolves.toBeUndefined()
87
+
88
+ // Wait for the SNIPPET_SAVED event to be detected
89
+ expect(snippetSavedPromise).resolves.toBeUndefined()
90
+ }, 20_000)
package/bun.lockb DELETED
Binary file