@tscircuit/cli 0.0.5 → 0.0.6

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.
@@ -0,0 +1,296 @@
1
+ import kleur from "kleur"
2
+ import { z } from "zod"
3
+ import { AppContext } from "../../util/app-context"
4
+ import * as Path from "path"
5
+ import * as fs from "fs/promises"
6
+ import { readFileSync } from "fs"
7
+ import { getAllPackageFiles } from "lib/util/get-all-package-files"
8
+ import prompts from "prompts"
9
+ import { getGeneratedReadme } from "../init/get-generated-readme"
10
+ import { soupify } from "../../soupify"
11
+ import { inferExportNameFromSource } from "../dev/infer-export-name-from-source"
12
+ import $ from "dax-sh"
13
+ import semver from "semver"
14
+ import { unlink } from "fs/promises"
15
+
16
+ export const publish = async (ctx: AppContext, args: any) => {
17
+ const params = z
18
+ .object({
19
+ increment: z.boolean().optional(),
20
+ patch: z.boolean().optional(),
21
+ lock: z.boolean().optional(),
22
+ })
23
+ .parse(args)
24
+ if (typeof Bun === "undefined") {
25
+ console.log(
26
+ kleur.red(
27
+ "\n\n----------------------------------\nBun is currently required for publishing packages to the tscircuit registry. Try installing bun:\nhhttps://bun.sh/docs/installation\n\n---------------------"
28
+ )
29
+ )
30
+ process.exit(1)
31
+ }
32
+
33
+ const shouldIncrement = params.increment || params.patch
34
+
35
+ if (!(await fs.exists(Path.join(ctx.cwd, "package.json")))) {
36
+ console.log(kleur.red("No package.json found in current directory"))
37
+ process.exit(1)
38
+ }
39
+
40
+ const packageJson = JSON.parse(
41
+ await readFileSync(Path.join(ctx.cwd, "package.json"), "utf-8")
42
+ )
43
+
44
+ // TODO possibly use esbuild here, it's got stuff like packages: "external"
45
+ await Bun.build({
46
+ root: ctx.cwd,
47
+ // TODO determine entrypoint in a more clever way e.g.
48
+ // - package.json "main"
49
+ entrypoints: ["index.ts"],
50
+
51
+ // Everything should be external since it's a node module, esbuild has a
52
+ // packages: "external" option for this
53
+ external: [
54
+ ...Object.keys(packageJson.dependencies || {}),
55
+ ...Object.keys(packageJson.devDependencies || {}),
56
+ ...Object.keys(packageJson.peerDependencies || {}),
57
+ ...Object.keys(packageJson.trustedDependencies || {}),
58
+ ],
59
+
60
+ outdir: "dist",
61
+
62
+ target: "node",
63
+ })
64
+
65
+ // Publish to npm??
66
+
67
+ // Upload to tscircuit registry
68
+ // 1. Get the package name and version from package.json
69
+ let { name, version } = packageJson
70
+ name = name.replace(/^@/, "") // remove leading @ if it exists
71
+ // 2.1 Check if package already exists, if it doesn't, create it
72
+ const existingPackage = await ctx.axios
73
+ .post("/packages/get", { name })
74
+ .then((r) => r.data.package)
75
+ .catch((e) => {
76
+ if (e.response?.data?.error?.error_code === "package_not_found")
77
+ return null
78
+ throw e
79
+ })
80
+
81
+ if (!existingPackage) {
82
+ if (!packageJson.name.includes("/")) {
83
+ console.log(
84
+ kleur.yellow(
85
+ `Package name "${packageJson.name}" is not scoped. Scoped package names are recommended on the tscircuit registry.`
86
+ )
87
+ )
88
+ const myAccount = await ctx.axios
89
+ .get("/accounts/get")
90
+ .then((r) => r.data.account)
91
+ const newScopedName = `${myAccount.github_username}/${packageJson.name}`
92
+
93
+ const { confirmNameChange } = await prompts({
94
+ type: "confirm",
95
+ name: "confirmNameChange",
96
+ initial: true,
97
+ message: `Would you like to change the package name to the scoped name "@${newScopedName}"?`,
98
+ })
99
+
100
+ if (confirm === undefined) {
101
+ console.log(kleur.red("Aborted."))
102
+ process.exit(1)
103
+ }
104
+
105
+ if (confirmNameChange) {
106
+ packageJson.name = `@${newScopedName}`
107
+ await fs.writeFile(
108
+ Path.join(ctx.cwd, "package.json"),
109
+ JSON.stringify(packageJson, null, 2)
110
+ )
111
+ name = newScopedName
112
+ }
113
+ }
114
+
115
+ console.log(
116
+ kleur.green(
117
+ `Creating package "${packageJson.name}" on tscircuit registry...`
118
+ )
119
+ )
120
+ let description = packageJson.description
121
+ if (!description) {
122
+ description = (
123
+ await prompts({
124
+ type: "text",
125
+ name: "description",
126
+ message: "Enter a description for the package",
127
+ })
128
+ ).description
129
+ }
130
+ await ctx.axios
131
+ .post("/packages/create", { name, description })
132
+ .then((r) => r.data.package)
133
+ }
134
+ // 2.2 Check if package release already exists
135
+ const existingRelease = await ctx.axios
136
+ .post("/package_releases/get", {
137
+ package_name_with_version: `${name}@${version}`,
138
+ })
139
+ .then((r) => r.data.package_release)
140
+ .catch((e) => {
141
+ if (e.response?.data?.error?.error_code === "package_release_not_found")
142
+ return null
143
+ throw e
144
+ })
145
+ // 3. If it does, ask to increment the version or update the existing release, if increment is specified, increment the version automatically
146
+ if (existingRelease) {
147
+ console.log(
148
+ kleur.gray(`Package release already exists: ${name}@${version}`)
149
+ )
150
+ if (shouldIncrement) {
151
+ console.log(
152
+ kleur.green(
153
+ `Incrementing version from ${version} to ${semver.inc(
154
+ version,
155
+ "patch"
156
+ )}...`
157
+ )
158
+ )
159
+ version = semver.inc(version, "patch")
160
+ packageJson.version = version
161
+ await fs.writeFile(
162
+ Path.join(ctx.cwd, "package.json"),
163
+ JSON.stringify(packageJson, null, 2)
164
+ )
165
+ } else {
166
+ console.log(
167
+ kleur.blue(
168
+ `Want to increment the version and publish a new release? Use "--increment"!`
169
+ )
170
+ )
171
+
172
+ throw new Error("Package release already exists")
173
+ }
174
+ }
175
+ // 4. Create new package_release
176
+ const newRelease = await ctx.axios
177
+ .post("/package_releases/create", {
178
+ package_name_with_version: `${name}@${version}`,
179
+ is_latest: false, // only make it latest when locking
180
+ })
181
+ .then((r) => r.data.package_release)
182
+ // 5. Upload package_files
183
+ const filePaths = await getAllPackageFiles(ctx)
184
+ if (!filePaths.includes("README.md")) {
185
+ console.log(
186
+ kleur.yellow(
187
+ "No README.md found in package files. A README.md is recommended on the tscircuit registry."
188
+ )
189
+ )
190
+ const { confirmReadme } = await prompts({
191
+ type: "confirm",
192
+ name: "confirmReadme",
193
+ initial: true,
194
+ message: "Would you like to add a README.md?",
195
+ })
196
+
197
+ if (confirmReadme === undefined) {
198
+ console.log(kleur.red("Aborted."))
199
+ process.exit(1)
200
+ }
201
+
202
+ if (confirmReadme) {
203
+ await fs.writeFile(
204
+ Path.join(ctx.cwd, "README.md"),
205
+ getGeneratedReadme({ name })
206
+ )
207
+ filePaths.push("README.md")
208
+ }
209
+ }
210
+
211
+ for (const filePath of filePaths) {
212
+ const fileContent = await fs.readFile(Path.join(ctx.cwd, filePath))
213
+
214
+ await ctx.axios
215
+ .post("/package_files/create", {
216
+ file_path: filePath,
217
+ content_text: fileContent.toString(),
218
+ package_name_with_version: `${name}@${version}`,
219
+ })
220
+ .then((r) => r.data.package_file)
221
+ }
222
+ // 6. Upload package_examples
223
+ const exampleFilePaths = filePaths.filter((fp) => fp.startsWith("examples/"))
224
+ for (const filePath of exampleFilePaths) {
225
+ const fileContent = (
226
+ await fs.readFile(Path.join(ctx.cwd, filePath))
227
+ ).toString()
228
+
229
+ const exportName = inferExportNameFromSource(fileContent)
230
+
231
+ const tscircuit_soup = await soupify({
232
+ filePath,
233
+ exportName,
234
+ }).catch((e) => e)
235
+
236
+ if (tscircuit_soup instanceof Error) {
237
+ console.log(
238
+ kleur.red(`Error soupifying ${filePath}: ${tscircuit_soup}, skipping`)
239
+ )
240
+ continue
241
+ }
242
+
243
+ await ctx.axios
244
+ .post("/package_examples/create", {
245
+ file_path: filePath,
246
+ package_name_with_version: `${name}@${version}`,
247
+ export_name: exportName,
248
+ source_content: fileContent,
249
+ tscircuit_soup,
250
+ })
251
+ .then((r) => r.data.package_example)
252
+ }
253
+
254
+ // 7. Create tarball and upload
255
+ const tmpTarballPath = Path.join(
256
+ ctx.cwd,
257
+ ".tscircuit/tmp",
258
+ `${name.replace(/\//g, "-")}-${version}.tgz`
259
+ )
260
+ await fs.mkdir(Path.dirname(tmpTarballPath), { recursive: true })
261
+ const npm_pack_outputs = await $`cd ${
262
+ ctx.cwd
263
+ } && npm pack --json --pack-destination ${Path.dirname(
264
+ tmpTarballPath
265
+ )}`.json()
266
+
267
+ if (!(await fs.exists(tmpTarballPath))) {
268
+ console.log(kleur.red(`Couldn't find tarball at ${tmpTarballPath}`))
269
+ process.exit(1)
270
+ }
271
+
272
+ // Upload tarball
273
+ await ctx.axios.post("/package_files/create", {
274
+ file_path: `.tscircuit-internal/tarball.tgz`,
275
+ content_base64: (await fs.readFile(tmpTarballPath)).toString("base64"),
276
+ package_name_with_version: `${name}@${version}`,
277
+ is_release_tarball: true,
278
+ npm_pack_output: npm_pack_outputs?.[0],
279
+ })
280
+
281
+ // Clean up .tscircuit/tmp
282
+ await unlink(tmpTarballPath)
283
+
284
+ // 8. Lock/set release to latest version
285
+ await ctx.axios.post("/package_releases/update", {
286
+ package_name_with_version: `${name}@${version}`,
287
+ is_locked: params.lock ? true : false,
288
+ is_latest: true,
289
+ })
290
+
291
+ console.log(
292
+ kleur.green(
293
+ `Published ${name}@${version}!\nhttps://registry.tscircuit.com/${name}`
294
+ )
295
+ )
296
+ }
@@ -0,0 +1,31 @@
1
+ import kleur from "kleur"
2
+ import { AppContext } from "lib/util/app-context"
3
+ import { z } from "zod"
4
+ import { createOrModifyNpmrc } from "./init/create-or-modify-npmrc"
5
+ import $ from "dax-sh"
6
+
7
+ export const removeCmd = async (ctx: AppContext, args: any) => {
8
+ const params = z
9
+ .object({
10
+ packages: z.array(z.string()),
11
+ flags: z.object({}).optional().default({}),
12
+ })
13
+ .parse(args)
14
+
15
+ params.packages = params.packages.map((p) => p.replace(/^@/, ""))
16
+
17
+ await createOrModifyNpmrc({ quiet: true }, ctx)
18
+
19
+ $.cd(ctx.cwd)
20
+
21
+ const flagsString = ""
22
+
23
+ const cmd = `npm remove ${flagsString} ${params.packages
24
+ .map((p) => `@tsci/${p.replace(/\//, ".")}`)
25
+ .join(" ")}`
26
+ console.log(kleur.gray(`> ${cmd}`))
27
+
28
+ await $`npm remove ${flagsString} ${params.packages
29
+ .map((p) => `@tsci/${p.replace(/\//, ".")}`)
30
+ .join(" ")}`
31
+ }
@@ -0,0 +1,31 @@
1
+ import kleur from "kleur"
2
+ import { AppContext } from "lib/util/app-context"
3
+ import { z } from "zod"
4
+ import { createOrModifyNpmrc } from "./init/create-or-modify-npmrc"
5
+ import $ from "dax-sh"
6
+
7
+ export const uninstallCmd = async (ctx: AppContext, args: any) => {
8
+ const params = z
9
+ .object({
10
+ packages: z.array(z.string()),
11
+ flags: z.object({}).optional().default({}),
12
+ })
13
+ .parse(args)
14
+
15
+ params.packages = params.packages.map((p) => p.replace(/^@/, ""))
16
+
17
+ await createOrModifyNpmrc({ quiet: true }, ctx)
18
+
19
+ $.cd(ctx.cwd)
20
+
21
+ const flagsString = ""
22
+
23
+ const cmd = `npm uninstall ${flagsString} ${params.packages
24
+ .map((p) => `@tsci/${p.replace(/\//, ".")}`)
25
+ .join(" ")}`
26
+ console.log(kleur.gray(`> ${cmd}`))
27
+
28
+ await $`npm uninstall ${flagsString} ${params.packages
29
+ .map((p) => `@tsci/${p.replace(/\//, ".")}`)
30
+ .join(" ")}`
31
+ }
@@ -21,7 +21,7 @@ import * as CMDFN from "lib/cmd-fns"
21
21
  // | /package_examples/create | Create a new package example |
22
22
 
23
23
  export const getProgram = (ctx: AppContext) => {
24
- const cmd = new Command("tsck")
24
+ const cmd = new Command("tsci")
25
25
 
26
26
  cmd.version(packageJson.version)
27
27
 
@@ -139,7 +139,7 @@ export const getProgram = (ctx: AppContext) => {
139
139
 
140
140
  packageFiles
141
141
  .command("upload-directory")
142
- .requiredOption("--directory <directory>", "Directory to upload")
142
+ .requiredOption("--dir <dir>", "Directory to upload")
143
143
  .action((args) => CMDFN.packageFilesUploadDirectory(ctx, args))
144
144
 
145
145
  const packageExamples = cmd.command("package_examples")
@@ -159,11 +159,16 @@ export const getProgram = (ctx: AppContext) => {
159
159
  .option("--export <export>", "Name of export to soupify")
160
160
  .action((args) => CMDFN.packageExamplesCreate(ctx, args))
161
161
 
162
- cmd.command("publish").action((args) => CMDFN.publish(ctx, args))
162
+ cmd
163
+ .command("publish")
164
+ .option("--increment", "Increase patch version")
165
+ .option("--patch", "Increase patch version")
166
+ .option("--lock", "Lock the release after publishing to prevent changes")
167
+ .action((args) => CMDFN.publish(ctx, args))
163
168
 
164
169
  cmd
165
170
  .command("version")
166
- .action(() => console.log(`tsck v${packageJson.version}`))
171
+ .action(() => console.log(`tsci v${packageJson.version}`))
167
172
 
168
173
  cmd.command("login").action((args) => CMDFN.authLogin(ctx, args))
169
174
  cmd.command("logout").action((args) => CMDFN.authLogout(ctx, args))
@@ -196,9 +201,45 @@ export const getProgram = (ctx: AppContext) => {
196
201
  )
197
202
  .action((args) => CMDFN.init(ctx, args))
198
203
 
199
- cmd.command("add").action((args) => {})
200
- cmd.command("remove").action((args) => {})
201
- cmd.command("install").action((args) => {})
204
+ cmd
205
+ .command("add")
206
+ .argument(
207
+ "<packages...>",
208
+ "Packages to install from registry.tscircuit.com, optionally with version"
209
+ )
210
+ .option("-D, --dev", "Add to devDependencies")
211
+ .action((packages, flags) => CMDFN.add(ctx, { packages, flags }))
212
+
213
+ cmd
214
+ .command("remove")
215
+ .argument("<packages...>", "Packages to remove")
216
+ .action((packages, flags) => CMDFN.remove(ctx, { packages, flags }))
217
+
218
+ cmd
219
+ .command("install")
220
+ .argument(
221
+ "<packages...>",
222
+ "Packages to install from registry.tscircuit.com, optionally with version"
223
+ )
224
+ .option("-D, --dev", "Add to devDependencies")
225
+ .action((packages, flags) => CMDFN.install(ctx, { packages, flags }))
226
+
227
+ cmd
228
+ .command("uninstall")
229
+ .argument("<packages...>", "Packages to uninstall")
230
+ .action((packages, flags) => CMDFN.install(ctx, { packages, flags }))
231
+
232
+ const devServerCmd = cmd.command("dev-server")
233
+
234
+ devServerCmd
235
+ .command("upload")
236
+ .option(
237
+ "--dir <dir>",
238
+ "Directory to upload (defaults to current directory)"
239
+ )
240
+ .option("-w, --watch", "Watch for changes")
241
+ .option("-p, --port", "Port dev server is running on (default: 3020)")
242
+ .action((args) => CMDFN.devServerUpload(ctx, args))
202
243
 
203
244
  return cmd
204
245
  }
@@ -18,7 +18,7 @@ export type CliArgs = {
18
18
  export const createContextAndRunProgram = async (process_args: any) => {
19
19
  const args = minimist(process_args)
20
20
 
21
- const global_config = new Configstore("tsck")
21
+ const global_config = new Configstore("tsci")
22
22
  const current_profile =
23
23
  args.profile ?? global_config.get("current_profile") ?? "default"
24
24
  const profile_config: typeof global_config = {
@@ -0,0 +1,35 @@
1
+ import * as fs from "fs/promises"
2
+ import { AppContext } from "./app-context"
3
+ import * as Glob from "glob"
4
+ import ignore from "ignore"
5
+
6
+ /**
7
+ * Get all package files
8
+ *
9
+ * Package files are any files that aren't explicitly ignored in the
10
+ * .npmignore (or if that doesn't exist, the .gitignore). All package
11
+ * files must have a .ts or .tsx extension.
12
+ *
13
+ * Returns an array of files paths.
14
+ */
15
+ export const getAllPackageFiles = async (
16
+ ctx: AppContext
17
+ ): Promise<Array<string>> => {
18
+ const gitignore = await fs
19
+ .readFile("./.gitignore")
20
+ .then((b) => b.toString().split("\n").filter(Boolean))
21
+ .catch((e) => null)
22
+
23
+ const npmignore = await fs
24
+ .readFile("./.promptignore")
25
+ .then((b) => b.toString().split("\n").filter(Boolean))
26
+ .catch((e) => null)
27
+
28
+ const ig = ignore().add([
29
+ ...(npmignore ?? gitignore ?? []),
30
+ ".tscircuit",
31
+ "node_modules/*",
32
+ ])
33
+
34
+ return Glob.globSync("**/*.{ts,tsx,md}", {}).filter((fp) => !ig.ignores(fp))
35
+ }
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "@tscircuit/cli",
3
- "version": "0.0.5",
3
+ "version": "0.0.6",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Command line tool for developing, publishing and installing tscircuit circuits",
7
7
  "main": "./dist/cli.js",
8
8
  "scripts": {
9
+ "bootstrap": "bun i && cd dev-server-api && bun i && cd ../dev-server-frontend && bun i",
9
10
  "start": "bun cli.ts",
10
11
  "start:dev-server:dev": "TSCI_DEV_SERVER_DB=$(pwd)/.tscircuit/dev-server.db concurrently 'cd dev-server-api && bun start' 'cd dev-server-frontend && bun start'",
11
12
  "start:dev-server": "bun build:dev-server && bun cli.ts dev -y --cwd ./tests/assets/example-project",
@@ -16,8 +17,7 @@
16
17
  },
17
18
  "bin": {
18
19
  "tscircuit": "./dist/cli.js",
19
- "tsci": "./dist/cli.js",
20
- "tsck": "./dist/cli.js"
20
+ "tsci": "./dist/cli.js"
21
21
  },
22
22
  "keywords": [],
23
23
  "author": "",
@@ -35,7 +35,9 @@
35
35
  "dax-sh": "^0.39.2",
36
36
  "delay": "^6.0.0",
37
37
  "edgespec": "^0.0.69",
38
+ "glob": "^10.3.10",
38
39
  "hono": "^4.1.0",
40
+ "ignore": "^5.3.1",
39
41
  "kleur": "^4.1.5",
40
42
  "kysely-bun-sqlite": "^0.3.2",
41
43
  "lodash": "^4.17.21",
@@ -43,9 +45,10 @@
43
45
  "minimist": "^1.2.8",
44
46
  "node-persist": "^4.0.1",
45
47
  "open": "^10.1.0",
46
- "perfect-cli": "1.0.13",
48
+ "perfect-cli": "^1.0.16",
47
49
  "prompts": "^2.4.2",
48
50
  "react": "^18.2.0",
51
+ "semver": "^7.6.0",
49
52
  "zod": "latest"
50
53
  },
51
54
  "devDependencies": {
@@ -58,9 +61,10 @@
58
61
  "@types/node": "^20.10.6",
59
62
  "@types/prompts": "^2.4.9",
60
63
  "@types/react": "^18.2.64",
64
+ "@types/semver": "^7.5.8",
61
65
  "ava": "^6.1.1",
62
66
  "concurrently": "^8.2.2",
63
67
  "tsx": "^4.7.1",
64
68
  "typescript": "^5.3.3"
65
69
  }
66
- }
70
+ }
@@ -0,0 +1,18 @@
1
+ # seveibar/example-project-3
2
+
3
+ To develop and view the examples, run `tsci dev` and open [http://localhost:3020](http://localhost:3020) in your browser.
4
+
5
+ ## Developing
6
+
7
+ You should install an editor like [VS Code](https://code.visualstudio.com/) with a typescript extension. Many web developers already have this.
8
+
9
+ Usually, you'll want to develop some named circuits inside of the `lib` directory,
10
+ export them in the `index.ts` file, then show how to use them in the `examples` directory.
11
+
12
+ Any file in `examples` will be automatically loaded and appear in the browser preview when you run `tsci dev`. It auto-reloads when you make changes, no need to reload the page or re-run the command.
13
+
14
+ > [!TIP] Make sure to replace this README with some details about your project and how to use it.
15
+
16
+ ## Publishing
17
+
18
+ After you're satisfied with your project, publish it on the [tscircuit registry](https://registry.tscircuit.com) with `tsci publish`
@@ -0,0 +1 @@
1
+ export * from "./src/MyCircuit"
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "example-project",
3
- "version": "1.2.3",
3
+ "version": "1.2.26",
4
+ "main": "dist/index.js",
4
5
  "dependencies": {
5
6
  "@tscircuit/builder": "^1.5.4",
6
7
  "@tscircuit/react-fiber": "^1.0.6"
@@ -1,7 +1,7 @@
1
1
  import { test, expect, describe } from "bun:test"
2
2
  import { $ } from "bun"
3
3
 
4
- test.skip("tsck init", async () => {
4
+ test.skip("tsci init", async () => {
5
5
  await $`rm -rf ./tests/example-init`
6
6
  console.log(
7
7
  await $`bun cli.ts init --name init-test --dir ./tests/example-init`.text()