@tscircuit/cli 0.1.23 → 0.1.25

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.
@@ -2,6 +2,53 @@ import type { Command } from "commander"
2
2
  import { getKy } from "lib/registry-api/get-ky"
3
3
  import * as fs from "node:fs"
4
4
  import * as path from "node:path"
5
+ import { execSync } from "node:child_process"
6
+
7
+ // Detect the package manager being used in the project
8
+ const detectPackageManager = (): string => {
9
+ const userAgent = process.env.npm_config_user_agent || ""
10
+ if (userAgent.startsWith("yarn")) return "yarn"
11
+ if (userAgent.startsWith("pnpm")) return "pnpm"
12
+ if (userAgent.startsWith("bun")) return "bun"
13
+
14
+ if (fs.existsSync("yarn.lock")) return "yarn"
15
+ if (fs.existsSync("pnpm-lock.yaml")) return "pnpm"
16
+ if (fs.existsSync("bun.lockb")) return "bun"
17
+
18
+ return "npm" // Default to npm
19
+ }
20
+
21
+ // Generate a React-compatible tsconfig.json
22
+ const generateTsConfig = (dir: string) => {
23
+ const tsconfigPath = path.join(dir, "tsconfig.json")
24
+ const tsconfigContent = JSON.stringify(
25
+ {
26
+ compilerOptions: {
27
+ target: "ES6",
28
+ module: "ESNext",
29
+ jsx: "react-jsx",
30
+ outDir: "dist",
31
+ strict: true,
32
+ esModuleInterop: true,
33
+ moduleResolution: "node",
34
+ skipLibCheck: true,
35
+ forceConsistentCasingInFileNames: true,
36
+ resolveJsonModule: true,
37
+ sourceMap: true,
38
+ allowSyntheticDefaultImports: true,
39
+ experimentalDecorators: true,
40
+ },
41
+ },
42
+ null,
43
+ 2,
44
+ )
45
+ if (!fs.existsSync(tsconfigPath)) {
46
+ fs.writeFileSync(tsconfigPath, tsconfigContent.trimStart())
47
+ console.log(`Created: ${tsconfigPath}`)
48
+ } else {
49
+ console.log(`Skipped: ${tsconfigPath} already exists`)
50
+ }
51
+ }
5
52
 
6
53
  export const registerClone = (program: Command) => {
7
54
  program
@@ -89,7 +136,33 @@ export const registerClone = (program: Command) => {
89
136
  const npmrcPath = path.join(dirPath, ".npmrc")
90
137
  fs.writeFileSync(npmrcPath, "@tsci:registry=https://npm.tscircuit.com")
91
138
 
139
+ // Generate tsconfig.json
140
+ generateTsConfig(dirPath)
141
+
142
+ // Detect package manager and install dependencies
143
+ const packageManager = detectPackageManager()
144
+ console.log(`Detected package manager: ${packageManager}`)
145
+
146
+ // Install deps using the detected package manager
147
+ const dependencies = "@types/react @tscircuit/core"
148
+ try {
149
+ console.log("Installing dependencies...")
150
+ const installCommand =
151
+ packageManager === "yarn"
152
+ ? `cd ${dirPath} && yarn add -D ${dependencies}`
153
+ : packageManager === "pnpm"
154
+ ? `cd ${dirPath} && pnpm add -D ${dependencies}`
155
+ : packageManager === "bun"
156
+ ? `cd ${dirPath} && bun add -D ${dependencies}`
157
+ : `cd ${dirPath} && npm install -D ${dependencies}`
158
+ execSync(installCommand, { stdio: "inherit" })
159
+ console.log("Dependencies installed successfully.")
160
+ } catch (error) {
161
+ console.error("Failed to install dependencies:", error)
162
+ }
163
+
92
164
  console.log(`Successfully cloned to ./${author}.${snippetName}/`)
165
+ console.log(`Run "cd ${dirPath} && tsci dev" to start developing.`)
93
166
  } catch (error) {
94
167
  if (error instanceof Error) {
95
168
  console.error("Failed to clone snippet:", error.message)
@@ -9,6 +9,7 @@ import fs from "node:fs"
9
9
  import type { FileUpdatedEvent } from "lib/file-server/FileServerEvent"
10
10
  import * as chokidar from "chokidar"
11
11
  import { FilesystemTypesHandler } from "lib/dependency-analysis/FilesystemTypesHandler"
12
+ import { pushSnippet } from "lib/shared/push-snippet"
12
13
 
13
14
  export class DevServer {
14
15
  port: number
@@ -68,6 +69,11 @@ export class DevServer {
68
69
  this.handleFileUpdatedEventFromServer.bind(this),
69
70
  )
70
71
 
72
+ this.eventsWatcher.on(
73
+ "REQUEST_TO_SAVE_SNIPPET",
74
+ this.saveSnippet.bind(this),
75
+ )
76
+
71
77
  this.filesystemWatcher = chokidar.watch(this.projectDir, {
72
78
  persistent: true,
73
79
  ignoreInitial: true,
@@ -159,6 +165,31 @@ circuit.add(<MyCircuit />)
159
165
  }
160
166
  }
161
167
 
168
+ private async saveSnippet() {
169
+ const postEvent = async (
170
+ event: "FAILED_TO_SAVE_SNIPPET" | "SNIPPET_SAVED",
171
+ ) =>
172
+ this.fsKy.post("api/events/create", {
173
+ json: { event_type: event },
174
+ throwHttpErrors: false,
175
+ })
176
+
177
+ await pushSnippet({
178
+ filePath: this.componentFilePath,
179
+ onExit: (e) => {
180
+ console.error("Failed to save snippet", e)
181
+ postEvent("FAILED_TO_SAVE_SNIPPET")
182
+ },
183
+ onError: (e) => {
184
+ console.error("Failed to save snippet", e)
185
+ postEvent("FAILED_TO_SAVE_SNIPPET")
186
+ },
187
+ onSuccess: () => {
188
+ postEvent("SNIPPET_SAVED")
189
+ },
190
+ })
191
+ }
192
+
162
193
  async stop() {
163
194
  this.httpServer?.close()
164
195
  this.eventsWatcher?.stop()
package/cli/main.ts CHANGED
@@ -16,7 +16,7 @@ import { registerAuthPrintToken } from "./auth/print-token/register"
16
16
  import { registerAuthSetToken } from "./auth/set-token/register"
17
17
  import { registerPush } from "./push/register"
18
18
 
19
- const program = new Command()
19
+ export const program = new Command()
20
20
 
21
21
  program
22
22
  .name("tsci")
@@ -1,9 +1,5 @@
1
+ import { pushSnippet } from "lib/shared/push-snippet"
1
2
  import type { Command } from "commander"
2
- import { cliConfig } from "lib/cli-config"
3
- import { getKy } from "lib/registry-api/get-ky"
4
- import * as fs from "node:fs"
5
- import * as path from "node:path"
6
- import semver from "semver"
7
3
 
8
4
  export const registerPush = (program: Command) => {
9
5
  program
@@ -11,189 +7,11 @@ export const registerPush = (program: Command) => {
11
7
  .description("Save snippet code to Registry API")
12
8
  .argument("[file]", "Path to the snippet file")
13
9
  .action(async (filePath?: string) => {
14
- const sessionToken = cliConfig.get("sessionToken")
15
- if (!sessionToken) {
16
- console.error("You need to log in to save snippet.")
17
- process.exit(1)
18
- }
19
-
20
- let snippetFilePath: string | null = null
21
- if (filePath) {
22
- snippetFilePath = path.resolve(filePath)
23
- } else {
24
- const defaultEntrypoint = path.resolve("index.tsx")
25
- if (fs.existsSync(defaultEntrypoint)) {
26
- snippetFilePath = defaultEntrypoint
27
- console.log("No file provided. Using 'index.tsx' as the entrypoint.")
28
- } else {
29
- console.error(
30
- "No entrypoint found. Run 'tsci init' to bootstrap a basic project.",
31
- )
32
- process.exit(1)
33
- }
34
- }
35
-
36
- const packageJsonPath = path.resolve(
37
- path.join(path.dirname(snippetFilePath), "package.json"),
38
- )
39
- let packageJson: { name?: string; author?: string; version?: string } = {}
40
- if (fs.existsSync(packageJsonPath)) {
41
- try {
42
- packageJson = JSON.parse(fs.readFileSync(packageJsonPath).toString())
43
- } catch {
44
- console.error("Invalid package.json provided")
45
- process.exit(1)
46
- }
47
- }
48
-
49
- if (!fs.existsSync(snippetFilePath)) {
50
- console.error(`File not found: ${snippetFilePath}`)
51
- process.exit(1)
52
- }
53
-
54
- const ky = getKy()
55
- const packageName = (
56
- packageJson.name ?? path.parse(snippetFilePath).name
57
- ).replace(/^@/, "")
58
-
59
- const packageAuthor =
60
- packageJson.author?.split(" ")[0] ?? cliConfig.get("githubUsername")
61
-
62
- const packageIdentifier = `${packageAuthor}/${packageName}`
63
-
64
- let packageVersion =
65
- packageJson.version ??
66
- (await ky
67
- .post<{
68
- error?: { error_code: string }
69
- package_releases?: { version: string; is_latest: boolean }[]
70
- }>("package_releases/list", {
71
- json: { package_name: packageIdentifier },
72
- })
73
- .json()
74
- .then(
75
- (response) =>
76
- response.package_releases?.[response.package_releases.length - 1]
77
- ?.version,
78
- )
79
- .catch((error) => {
80
- console.error("Failed to retrieve latest package version:", error)
81
- process.exit(1)
82
- }))
83
-
84
- if (!packageVersion) {
85
- console.log("Failed to retrieve package version.")
86
- process.exit(1)
87
- }
88
-
89
- const updatePackageJsonVersion = (newVersion?: string) => {
90
- if (packageJson.version) {
91
- try {
92
- packageJson.version = newVersion ?? packageVersion
93
- fs.writeFileSync(
94
- packageJsonPath,
95
- JSON.stringify(packageJson, null, 2),
96
- )
97
- } catch (error) {
98
- console.error("Failed to update package.json version:", error)
99
- }
100
- }
101
- }
102
-
103
- const doesPackageExist = await ky
104
- .post<{ error?: { error_code: string } }>("packages/get", {
105
- json: { name: packageIdentifier },
106
- throwHttpErrors: false,
107
- })
108
- .json()
109
- .then(
110
- (response) => !(response.error?.error_code === "package_not_found"),
111
- )
112
-
113
- if (!doesPackageExist) {
114
- await ky
115
- .post("packages/create", {
116
- json: { name: packageIdentifier },
117
- headers: { Authorization: `Bearer ${sessionToken}` },
118
- })
119
- .catch((error) => {
120
- console.error("Error creating package:", error)
121
- process.exit(1)
122
- })
123
- }
124
-
125
- const doesReleaseExist = await ky
126
- .post<{
127
- error?: { error_code: string }
128
- package_release?: { version: string }
129
- }>("package_releases/get", {
130
- json: {
131
- package_name_with_version: `${packageIdentifier}@${packageVersion}`,
132
- },
133
- throwHttpErrors: false,
134
- })
135
- .json()
136
- .then((response) => {
137
- if (response.package_release?.version) {
138
- packageVersion = response.package_release.version
139
- updatePackageJsonVersion(response.package_release.version)
140
- return true
141
- }
142
- return !(response.error?.error_code === "package_release_not_found")
143
- })
144
-
145
- if (doesReleaseExist) {
146
- const bumpedVersion = semver.inc(packageVersion, "patch")!
147
- console.log(
148
- `Incrementing Package Version ${packageVersion} -> ${bumpedVersion}`,
149
- )
150
- packageVersion = bumpedVersion
151
- updatePackageJsonVersion(packageVersion)
152
- }
153
-
154
- await ky
155
- .post("package_releases/create", {
156
- json: {
157
- package_name_with_version: `${packageIdentifier}@${packageVersion}`,
158
- },
159
- throwHttpErrors: false,
160
- })
161
- .catch((error) => {
162
- console.error("Error creating release:", error)
163
- process.exit(1)
164
- })
165
-
166
- console.log("\n")
167
-
168
- const directoryFiles = fs.readdirSync(path.dirname(snippetFilePath))
169
- for (const file of directoryFiles) {
170
- const fileExtension = path.extname(file).replace(".", "")
171
- if (!["json", "tsx", "ts"].includes(fileExtension)) continue
172
-
173
- const fileContent =
174
- fs
175
- .readFileSync(path.join(path.dirname(snippetFilePath), file))
176
- .toString() ?? ""
177
- await ky
178
- .post("package_files/create", {
179
- json: {
180
- file_path: file,
181
- content_text: fileContent,
182
- package_name_with_version: `${packageIdentifier}@${packageVersion}`,
183
- },
184
- throwHttpErrors: false,
185
- })
186
- .then(() => {
187
- console.log(`Uploaded file ${file} to the registry.`)
188
- })
189
- .catch((error) => {
190
- console.error(`Error uploading file ${file}:`, error)
191
- })
192
- }
193
-
194
- console.log(
195
- `\nšŸŽ‰ Successfully pushed package ${packageIdentifier}@${packageVersion} to the registry!${Bun.color("blue", "ansi")}`,
196
- `https://tscircuit.com/${packageIdentifier} \x1b[0m`,
197
- )
10
+ await pushSnippet({
11
+ filePath,
12
+ onExit: (code) => process.exit(code),
13
+ onError: (message) => console.error(message),
14
+ onSuccess: (message) => console.log(message),
15
+ })
198
16
  })
199
17
  }