@tscircuit/cli 0.1.26 → 0.1.27

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,53 +2,8 @@ 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
+ import { setupTsciProject } from "lib/shared/setup-tsci-packages"
6
+ import { generateTsConfig } from "lib/shared/generate-ts-config"
52
7
 
53
8
  export const registerClone = (program: Command) => {
54
9
  program
@@ -56,120 +11,92 @@ export const registerClone = (program: Command) => {
56
11
  .description("Clone a snippet from the registry")
57
12
  .argument("<snippet>", "Snippet to clone (e.g. author/snippetName)")
58
13
  .action(async (snippetPath: string) => {
59
- let author: string
60
- let snippetName: string
61
- if (!snippetPath.startsWith("@tsci/") && snippetPath.includes("/")) {
62
- ;[author, snippetName] = snippetPath.split("/")
63
- } else {
64
- const trimmedPath = snippetPath.replace("@tsci/", "")
65
- const firstDotIndex = trimmedPath.indexOf(".")
66
- author = trimmedPath.slice(0, firstDotIndex)
67
- snippetName = trimmedPath.slice(firstDotIndex + 1)
68
- }
69
-
70
- if (!author || !snippetName) {
14
+ const match = snippetPath.match(/^(?:@tsci\/)?([^/.]+)[/.](.+)$/)
15
+ if (!match) {
71
16
  console.error(
72
- "Invalid snippet path. Use format: author/snippetName, author.snippetName or @tsci/author.snippetName",
17
+ "Invalid snippet path. Use format: author/snippetName, author.snippetName, or @tsci/author.snippetName",
73
18
  )
74
19
  process.exit(1)
75
20
  }
76
21
 
77
- const ky = getKy()
22
+ const [, author, snippetName] = match
23
+ console.log(`Cloning ${author}/${snippetName}...`)
78
24
 
25
+ const ky = getKy()
26
+ let packageFileList
79
27
  try {
80
- console.log(`Cloning ${author}/${snippetName}...`)
81
-
82
- const packageFileList = await ky
83
- .post<{
84
- package_files: Array<{
85
- package_file_id: string
86
- package_release_id: string
87
- file_path: string
88
- created_at: string
89
- }>
90
- }>("package_files/list", {
91
- json: {
92
- package_name: `${author}/${snippetName}`,
93
- use_latest_version: true,
28
+ packageFileList = await ky
29
+ .post<{ package_files: Array<{ file_path: string }> }>(
30
+ "package_files/list",
31
+ {
32
+ json: {
33
+ package_name: `${author}/${snippetName}`,
34
+ use_latest_version: true,
35
+ },
94
36
  },
95
- })
37
+ )
96
38
  .json()
39
+ } catch (error) {
40
+ console.error(
41
+ "Failed to fetch package files:",
42
+ error instanceof Error ? error.message : error,
43
+ )
44
+ process.exit(1)
45
+ }
97
46
 
98
- // Create directory if it doesn't exist
99
- const dirPath = `./${author}.${snippetName}`
100
- if (!fs.existsSync(dirPath)) {
101
- fs.mkdirSync(dirPath)
102
- }
47
+ const dirPath = path.resolve(`${author}.${snippetName}`)
48
+ fs.mkdirSync(dirPath, { recursive: true })
103
49
 
104
- // Download each file that doesn't start with dist/
105
- for (const fileInfo of packageFileList.package_files) {
106
- const filePath = fileInfo.file_path.startsWith("/")
107
- ? fileInfo.file_path.slice(1)
108
- : fileInfo.file_path
50
+ for (const fileInfo of packageFileList.package_files) {
51
+ const filePath = fileInfo.file_path.replace(/^\/|dist\//g, "")
52
+ if (!filePath) continue
109
53
 
110
- if (filePath.startsWith("dist/")) continue
54
+ const fullPath = path.join(dirPath, filePath)
55
+ fs.mkdirSync(path.dirname(fullPath), { recursive: true })
111
56
 
57
+ try {
112
58
  const fileContent = await ky
113
- .post<{
114
- package_file: {
115
- content_text: string
116
- }
117
- }>("package_files/get", {
118
- json: {
119
- package_name: `${author}/${snippetName}`,
120
- file_path: fileInfo.file_path,
59
+ .post<{ package_file: { content_text: string } }>(
60
+ "package_files/get",
61
+ {
62
+ json: {
63
+ package_name: `${author}/${snippetName}`,
64
+ file_path: fileInfo.file_path,
65
+ },
121
66
  },
122
- })
67
+ )
123
68
  .json()
124
69
 
125
- const fullPath = path.join(dirPath, filePath)
126
- const dirName = path.dirname(fullPath)
70
+ let fileText = fileContent.package_file.content_text
127
71
 
128
- // Create nested directories if they don't exist
129
- if (!fs.existsSync(dirName)) {
130
- fs.mkdirSync(dirName, { recursive: true })
72
+ // Ensure all .tsx files contain "import '@tscircuit/core';"
73
+ if (
74
+ filePath.endsWith(".tsx") &&
75
+ !fileText.includes("@tscircuit/core")
76
+ ) {
77
+ fileText = `import "@tscircuit/core";\n\n${fileText}`
131
78
  }
132
79
 
133
- fs.writeFileSync(fullPath, fileContent.package_file.content_text)
80
+ fs.writeFileSync(fullPath, fileText)
81
+ } catch (error) {
82
+ console.warn(
83
+ `Skipping ${filePath} due to error:`,
84
+ error instanceof Error ? error.message : error,
85
+ )
134
86
  }
87
+ }
135
88
 
136
- const npmrcPath = path.join(dirPath, ".npmrc")
137
- fs.writeFileSync(npmrcPath, "@tsci:registry=https://npm.tscircuit.com")
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}`)
89
+ fs.writeFileSync(
90
+ path.join(dirPath, ".npmrc"),
91
+ "@tsci:registry=https://npm.tscircuit.com",
92
+ )
145
93
 
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
- }
94
+ generateTsConfig(dirPath)
95
+ setupTsciProject(dirPath)
163
96
 
164
- console.log(`Successfully cloned to ./${author}.${snippetName}/`)
165
- console.log(`Run "cd ${dirPath} && tsci dev" to start developing.`)
166
- } catch (error) {
167
- if (error instanceof Error) {
168
- console.error("Failed to clone snippet:", error.message)
169
- } else {
170
- console.error("Failed to clone snippet:", error)
171
- }
172
- process.exit(1)
173
- }
97
+ console.log(`Successfully cloned to ${dirPath}/`)
98
+ console.log(
99
+ `Run "cd ${path.dirname(dirPath)} && tsci dev" to start developing.`,
100
+ )
174
101
  })
175
102
  }
@@ -1,136 +1,70 @@
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
+ import { setupTsciProject } from "lib/shared/setup-tsci-packages"
5
+ import { generateTsConfig } from "lib/shared/generate-ts-config"
6
+ import { writeFileIfNotExists } from "lib/shared/write-file-if-not-exists"
51
7
 
52
8
  export const registerInit = (program: Command) => {
53
9
  program
54
10
  .command("init")
55
- .description("Initialize a new TSCircuit project in the current directory")
56
- .action(() => {
57
- const currentDir = process.cwd()
58
- const indexFilePath = path.join(currentDir, "index.tsx")
59
- const npmrcFilePath = path.join(currentDir, ".npmrc")
11
+ .description(
12
+ "Initialize a new TSCircuit project in the specified directory (or current directory if none is provided)",
13
+ )
14
+ .argument(
15
+ "[directory]",
16
+ "Directory name (optional, defaults to current directory)",
17
+ )
18
+ .action((directory?: string) => {
19
+ const projectDir = directory
20
+ ? path.resolve(process.cwd(), directory)
21
+ : process.cwd()
22
+
23
+ // Ensure the directory exists
24
+ fs.mkdirSync(projectDir, { recursive: true })
60
25
 
61
- // Content for index.tsx
62
- const indexContent = `
63
- import "@tscircuit/core"
26
+ // Create essential project files
27
+ writeFileIfNotExists(
28
+ path.join(projectDir, "index.tsx"),
29
+ `
30
+ import "@tscircuit/core";
64
31
 
65
32
  export default () => (
66
33
  <board width="10mm" height="10mm">
67
- <resistor
68
- resistance="1k"
69
- footprint="0402"
70
- name="R1"
71
- schX={3}
72
- pcbX={3}
73
- />
74
- <capacitor
75
- capacitance="1000pF"
76
- footprint="0402"
77
- name="C1"
78
- schX={-3}
79
- pcbX={-3}
80
- />
34
+ <resistor resistance="1k" footprint="0402" name="R1" schX={3} pcbX={3} />
35
+ <capacitor capacitance="1000pF" footprint="0402" name="C1" schX={-3} pcbX={-3} />
81
36
  <trace from=".R1 > .pin1" to=".C1 > .pin1" />
82
37
  </board>
83
38
  );
84
- `.trim()
39
+ `,
40
+ )
85
41
 
86
- // Content for .npmrc
87
- const npmrcContent = `
42
+ writeFileIfNotExists(
43
+ path.join(projectDir, ".npmrc"),
44
+ `
88
45
  @tsci:registry=https://npm.tscircuit.com
89
- `.trim()
90
-
91
- // Create index.tsx if it doesn't exist
92
- if (!fs.existsSync(indexFilePath)) {
93
- fs.writeFileSync(indexFilePath, indexContent.trimStart())
94
- console.log(`Created: ${indexFilePath}`)
95
- } else {
96
- console.log(`Skipped: ${indexFilePath} already exists`)
97
- }
98
-
99
- // Create .npmrc if it doesn't exist
100
- if (!fs.existsSync(npmrcFilePath)) {
101
- fs.writeFileSync(npmrcFilePath, npmrcContent.trimStart())
102
- console.log(`Created: ${npmrcFilePath}`)
103
- } else {
104
- console.log(`Skipped: ${npmrcFilePath} already exists`)
105
- }
106
-
107
- // Detect the package manager
108
- const packageManager = detectPackageManager()
109
- console.log(`Detected package manager: ${packageManager}`)
46
+ `,
47
+ )
110
48
 
111
- // Install deps using the detected package manager
112
- const dependencies = "@types/react @tscircuit/core"
49
+ // Setup project dependencies
113
50
  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.")
51
+ setupTsciProject(projectDir)
125
52
  } catch (error) {
126
53
  console.error("Failed to install dependencies:", error)
54
+ process.exit(1)
127
55
  }
128
56
 
129
57
  // Generate tsconfig.json
130
- generateTsConfig(currentDir)
58
+ try {
59
+ generateTsConfig(projectDir)
60
+ } catch (error) {
61
+ console.error("Failed to generate tsconfig.json:", error)
62
+ process.exit(1)
63
+ }
131
64
 
132
- console.log(
133
- `Initialization complete. Run "tsci dev" to start developing.`,
65
+ console.info(
66
+ `🎉 Initialization complete! Run ${directory ? `"cd ${directory}" & ` : ""}"tsci dev" to start developing.`,
134
67
  )
68
+ process.exit(0)
135
69
  })
136
70
  }