@tscircuit/cli 0.1.21 → 0.1.23

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.
@@ -1,6 +1,53 @@
1
1
  import type { Command } from "commander"
2
2
  import * as fs from "node:fs"
3
3
  import * as path from "node:path"
4
+ import { execSync } from "node:child_process"
5
+
6
+ // Detect the package manager being used in the project
7
+ const detectPackageManager = (): string => {
8
+ const userAgent = process.env.npm_config_user_agent || ""
9
+ if (userAgent.startsWith("yarn")) return "yarn"
10
+ if (userAgent.startsWith("pnpm")) return "pnpm"
11
+ if (userAgent.startsWith("bun")) return "bun"
12
+
13
+ if (fs.existsSync("yarn.lock")) return "yarn"
14
+ if (fs.existsSync("pnpm-lock.yaml")) return "pnpm"
15
+ if (fs.existsSync("bun.lockb")) return "bun"
16
+
17
+ return "npm" // Default to npm
18
+ }
19
+
20
+ // Generate a React-compatible tsconfig.json
21
+ const generateTsConfig = (dir: string) => {
22
+ const tsconfigPath = path.join(dir, "tsconfig.json")
23
+ const tsconfigContent = JSON.stringify(
24
+ {
25
+ compilerOptions: {
26
+ target: "ES6",
27
+ module: "ESNext",
28
+ jsx: "react-jsx",
29
+ outDir: "dist",
30
+ strict: true,
31
+ esModuleInterop: true,
32
+ moduleResolution: "node",
33
+ skipLibCheck: true,
34
+ forceConsistentCasingInFileNames: true,
35
+ resolveJsonModule: true,
36
+ sourceMap: true,
37
+ allowSyntheticDefaultImports: true,
38
+ experimentalDecorators: true,
39
+ },
40
+ },
41
+ null,
42
+ 2,
43
+ )
44
+ if (!fs.existsSync(tsconfigPath)) {
45
+ fs.writeFileSync(tsconfigPath, tsconfigContent.trimStart())
46
+ console.log(`Created: ${tsconfigPath}`)
47
+ } else {
48
+ console.log(`Skipped: ${tsconfigPath} already exists`)
49
+ }
50
+ }
4
51
 
5
52
  export const registerInit = (program: Command) => {
6
53
  program
@@ -8,11 +55,13 @@ export const registerInit = (program: Command) => {
8
55
  .description("Initialize a new TSCircuit project in the current directory")
9
56
  .action(() => {
10
57
  const currentDir = process.cwd()
11
-
12
58
  const indexFilePath = path.join(currentDir, "index.tsx")
13
59
  const npmrcFilePath = path.join(currentDir, ".npmrc")
14
60
 
61
+ // Content for index.tsx
15
62
  const indexContent = `
63
+ import "@tscircuit/core"
64
+
16
65
  export default () => (
17
66
  <board width="10mm" height="10mm">
18
67
  <resistor
@@ -32,12 +81,14 @@ export default () => (
32
81
  <trace from=".R1 > .pin1" to=".C1 > .pin1" />
33
82
  </board>
34
83
  );
35
- `
84
+ `.trim()
36
85
 
86
+ // Content for .npmrc
37
87
  const npmrcContent = `
38
88
  @tsci:registry=https://npm.tscircuit.com
39
- `
89
+ `.trim()
40
90
 
91
+ // Create index.tsx if it doesn't exist
41
92
  if (!fs.existsSync(indexFilePath)) {
42
93
  fs.writeFileSync(indexFilePath, indexContent.trimStart())
43
94
  console.log(`Created: ${indexFilePath}`)
@@ -45,6 +96,7 @@ export default () => (
45
96
  console.log(`Skipped: ${indexFilePath} already exists`)
46
97
  }
47
98
 
99
+ // Create .npmrc if it doesn't exist
48
100
  if (!fs.existsSync(npmrcFilePath)) {
49
101
  fs.writeFileSync(npmrcFilePath, npmrcContent.trimStart())
50
102
  console.log(`Created: ${npmrcFilePath}`)
@@ -52,6 +104,31 @@ export default () => (
52
104
  console.log(`Skipped: ${npmrcFilePath} already exists`)
53
105
  }
54
106
 
107
+ // Detect the package manager
108
+ const packageManager = detectPackageManager()
109
+ console.log(`Detected package manager: ${packageManager}`)
110
+
111
+ // Install deps using the detected package manager
112
+ const dependencies = "@types/react @tscircuit/core"
113
+ try {
114
+ console.log("Installing dependencies...")
115
+ const installCommand =
116
+ packageManager === "yarn"
117
+ ? `yarn add -D ${dependencies}`
118
+ : packageManager === "pnpm"
119
+ ? `pnpm add -D ${dependencies}`
120
+ : packageManager === "bun"
121
+ ? `bun add -D ${dependencies}`
122
+ : `npm install -D ${dependencies}`
123
+ execSync(installCommand, { stdio: "inherit" })
124
+ console.log("Dependencies installed successfully.")
125
+ } catch (error) {
126
+ console.error("Failed to install dependencies:", error)
127
+ }
128
+
129
+ // Generate tsconfig.json
130
+ generateTsConfig(currentDir)
131
+
55
132
  console.log(
56
133
  `Initialization complete. Run "tsci dev" to start developing.`,
57
134
  )
package/cli/main.ts CHANGED
@@ -14,6 +14,7 @@ import semver from "semver"
14
14
  import { registerExport } from "./export/register"
15
15
  import { registerAuthPrintToken } from "./auth/print-token/register"
16
16
  import { registerAuthSetToken } from "./auth/set-token/register"
17
+ import { registerPush } from "./push/register"
17
18
 
18
19
  const program = new Command()
19
20
 
@@ -28,6 +29,7 @@ registerInit(program)
28
29
 
29
30
  registerDev(program)
30
31
  registerClone(program)
32
+ registerPush(program)
31
33
 
32
34
  registerAuth(program)
33
35
  registerAuthLogin(program)
@@ -0,0 +1,199 @@
1
+ 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
+
8
+ export const registerPush = (program: Command) => {
9
+ program
10
+ .command("push")
11
+ .description("Save snippet code to Registry API")
12
+ .argument("[file]", "Path to the snippet file")
13
+ .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
+ )
198
+ })
199
+ }