@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.
- package/bun.lock +2260 -0
- package/cli/dev/DevServer.ts +31 -0
- package/cli/init/register.ts +80 -3
- package/cli/main.ts +1 -1
- package/cli/push/register.ts +7 -189
- package/dist/main.js +317 -225
- package/example-dir/snippet2-large-led-matrix.tsx +1 -1
- package/example-dir/tsconfig.json +17 -0
- package/example-dir/types.d.ts +20 -0
- package/lib/file-server/FileServerRoutes.ts +3 -1
- package/lib/server/EventsRoutes.ts +32 -0
- package/lib/shared/push-snippet.ts +198 -0
- package/package.json +3 -3
- package/tests/cli/init/init.test.ts +34 -0
- package/tests/test5-dev-server-save-snippet.test.ts +90 -0
- package/bun.lockb +0 -0
|
@@ -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
|
|
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
|
+
}
|
package/example-dir/types.d.ts
CHANGED
|
@@ -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
|
+
}
|
|
@@ -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.
|
|
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.
|
|
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": "
|
|
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
|