@tscircuit/fake-snippets 0.0.4 → 0.0.5

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.
@@ -17,6 +17,7 @@ import {
17
17
  packageReleaseSchema,
18
18
  packageSchema,
19
19
  Package,
20
+ PackageRelease,
20
21
  } from "./schema.ts"
21
22
  import { combine } from "zustand/middleware"
22
23
  import { seed as seedFn } from "./seed"
@@ -387,4 +388,33 @@ const initializer = combine(databaseSchema.parse({}), (set, get) => ({
387
388
  ...pkg,
388
389
  }
389
390
  },
391
+ getPackageReleaseById: (
392
+ package_release_id: string,
393
+ ): PackageRelease | undefined => {
394
+ const state = get()
395
+ return state.packageReleases.find(
396
+ (pr) => pr.package_release_id === package_release_id,
397
+ )
398
+ },
399
+ addPackageRelease: (
400
+ packageRelease: Omit<PackageRelease, "package_release_id">,
401
+ ): PackageRelease => {
402
+ const newPackageRelease = {
403
+ package_release_id: `package_release_${Date.now()}`,
404
+ ...packageRelease,
405
+ }
406
+ set((state) => ({
407
+ packageReleases: [...state.packageReleases, newPackageRelease],
408
+ }))
409
+ return newPackageRelease
410
+ },
411
+ updatePackageRelease: (packageRelease: PackageRelease): void => {
412
+ set((state) => ({
413
+ packageReleases: state.packageReleases.map((pr) =>
414
+ pr.package_release_id === packageRelease.package_release_id
415
+ ? packageRelease
416
+ : pr,
417
+ ),
418
+ }))
419
+ },
390
420
  }))
@@ -107,6 +107,8 @@ export const packageReleaseSchema = z.object({
107
107
  is_locked: z.boolean(),
108
108
  is_latest: z.boolean(),
109
109
  created_at: z.string().datetime(),
110
+ commit_sha: z.string().nullable().optional(),
111
+ license: z.string().nullable().optional(),
110
112
  })
111
113
  export type PackageRelease = z.infer<typeof packageReleaseSchema>
112
114
 
@@ -0,0 +1,10 @@
1
+ import * as ZT from "fake-snippets-api/lib/db/schema"
2
+
3
+ export const publicMapPackageRelease = (
4
+ internal_package_release: ZT.PackageRelease,
5
+ ): ZT.PackageRelease => {
6
+ return {
7
+ ...internal_package_release,
8
+ created_at: internal_package_release.created_at,
9
+ }
10
+ }
@@ -0,0 +1,83 @@
1
+ import { packageReleaseSchema } from "fake-snippets-api/lib/db/schema"
2
+ import { withRouteSpec } from "fake-snippets-api/lib/middleware/with-winter-spec"
3
+ import { publicMapPackageRelease } from "fake-snippets-api/lib/public-mapping/public-map-package-release"
4
+ import { z } from "zod"
5
+
6
+ export default withRouteSpec({
7
+ methods: ["POST"],
8
+ auth: "none",
9
+ jsonBody: z.object({
10
+ package_id: z.string().optional(),
11
+ version: z.string().optional(),
12
+ is_latest: z.boolean().optional(),
13
+ commit_sha: z.string().optional(),
14
+ package_name_with_version: z.string().optional(),
15
+ }),
16
+ jsonResponse: z.object({
17
+ ok: z.boolean(),
18
+ package_release: packageReleaseSchema,
19
+ }),
20
+ })(async (req, ctx) => {
21
+ let {
22
+ package_id,
23
+ is_latest = true,
24
+ version,
25
+ commit_sha,
26
+ package_name_with_version,
27
+ } = req.jsonBody
28
+
29
+ if (package_name_with_version && !version && !package_id) {
30
+ const [packageName, parsedVersion] = package_name_with_version.split("@")
31
+ const pkg = ctx.db.packages.find((p) => p.name === packageName)
32
+
33
+ if (!pkg) {
34
+ return ctx.error(404, {
35
+ error_code: "package_not_found",
36
+ message: `Package not found: ${packageName}`,
37
+ })
38
+ }
39
+
40
+ package_id = pkg.package_id
41
+ version = parsedVersion
42
+ }
43
+
44
+ if (!package_id || !version) {
45
+ return ctx.error(400, {
46
+ error_code: "missing_options",
47
+ message: "package_id and version are required",
48
+ })
49
+ }
50
+
51
+ // Check if version already exists
52
+ const existingRelease = ctx.db.packageReleases.find(
53
+ (pr) => pr.package_id === package_id && pr.version === version,
54
+ )
55
+
56
+ if (existingRelease) {
57
+ return ctx.error(400, {
58
+ error_code: "version_already_exists",
59
+ message: `Version ${version} already exists for this package`,
60
+ })
61
+ }
62
+
63
+ // Update previous latest if needed
64
+ if (is_latest) {
65
+ ctx.db.packageReleases
66
+ .filter((pr) => pr.package_id === package_id && pr.is_latest)
67
+ .forEach((pr) => (pr.is_latest = false))
68
+ }
69
+
70
+ const newPackageRelease = ctx.db.addPackageRelease({
71
+ package_id,
72
+ is_latest,
73
+ version,
74
+ is_locked: false,
75
+ created_at: new Date().toISOString(),
76
+ commit_sha: commit_sha ?? null,
77
+ })
78
+
79
+ return ctx.json({
80
+ ok: true,
81
+ package_release: publicMapPackageRelease(newPackageRelease),
82
+ })
83
+ })
@@ -0,0 +1,34 @@
1
+ import * as zt from "fake-snippets-api/lib/db/schema"
2
+ import { publicMapPackageRelease } from "fake-snippets-api/lib/public-mapping/public-map-package-release"
3
+ import { withRouteSpec } from "fake-snippets-api/lib/with-winter-spec"
4
+ import { z } from "zod"
5
+
6
+ export default withRouteSpec({
7
+ methods: ["POST"],
8
+ auth: "none",
9
+ jsonBody: z.object({
10
+ package_release_id: z.string().optional(),
11
+ package_name_with_version: z.string().optional(),
12
+ }),
13
+ jsonResponse: z.object({
14
+ ok: z.boolean(),
15
+ package_release: zt.packageReleaseSchema,
16
+ }),
17
+ })(async (req, ctx) => {
18
+ const { package_release_id } = req.jsonBody
19
+
20
+ const foundRelease =
21
+ package_release_id && ctx.db.getPackageReleaseById(package_release_id)
22
+
23
+ if (!foundRelease) {
24
+ return ctx.error(404, {
25
+ error_code: "package_release_not_found",
26
+ message: "Package release not found",
27
+ })
28
+ }
29
+
30
+ return ctx.json({
31
+ ok: true,
32
+ package_release: publicMapPackageRelease(foundRelease),
33
+ })
34
+ })
@@ -0,0 +1,77 @@
1
+ import { packageReleaseSchema } from "fake-snippets-api/lib/db/schema"
2
+ import { withRouteSpec } from "fake-snippets-api/lib/middleware/with-winter-spec"
3
+ import { publicMapPackageRelease } from "fake-snippets-api/lib/public-mapping/public-map-package-release"
4
+ import { z } from "zod"
5
+
6
+ export default withRouteSpec({
7
+ methods: ["POST"],
8
+ auth: "none",
9
+ jsonBody: z
10
+ .object({
11
+ package_id: z.string().optional(),
12
+ package_name: z.string().optional(),
13
+ is_latest: z.boolean().optional(),
14
+ version: z.string().optional(),
15
+ commit_sha: z.string().optional(),
16
+ })
17
+ .refine(({ package_id, package_name }) => {
18
+ if (package_id && package_name) {
19
+ return false
20
+ }
21
+ return true
22
+ }, "package_id and package_name are mutually exclusive")
23
+ .refine(({ package_id, package_name }) => {
24
+ if (!package_id && !package_name) {
25
+ return false
26
+ }
27
+ return true
28
+ }, "package_id or package_name is required"),
29
+ jsonResponse: z.object({
30
+ ok: z.boolean(),
31
+ package_releases: z.array(packageReleaseSchema),
32
+ }),
33
+ })(async (req, ctx) => {
34
+ const { package_id, package_name, is_latest, version, commit_sha } =
35
+ req.jsonBody
36
+
37
+ if (!package_id && !package_name && !is_latest && !version && !commit_sha) {
38
+ return ctx.error(400, {
39
+ error_code: "invalid_query",
40
+ message:
41
+ "At least one of package_id, package_name, is_latest, version or commit_sha is required",
42
+ })
43
+ }
44
+
45
+ let releases = ctx.db.packageReleases
46
+
47
+ // Apply filters
48
+ if (package_id) {
49
+ releases = releases.filter((pr) => pr.package_id === package_id)
50
+ }
51
+
52
+ if (package_name) {
53
+ const pkg = ctx.db.packages.find((p) => p.name === package_name)
54
+ if (pkg) {
55
+ releases = releases.filter((pr) => pr.package_id === pkg.package_id)
56
+ } else {
57
+ releases = []
58
+ }
59
+ }
60
+
61
+ if (is_latest !== undefined) {
62
+ releases = releases.filter((pr) => pr.is_latest === is_latest)
63
+ }
64
+
65
+ if (version) {
66
+ releases = releases.filter((pr) => pr.version === version)
67
+ }
68
+
69
+ if (commit_sha) {
70
+ releases = releases.filter((pr) => pr.commit_sha === commit_sha)
71
+ }
72
+
73
+ return ctx.json({
74
+ ok: true,
75
+ package_releases: releases.map((pr) => publicMapPackageRelease(pr)),
76
+ })
77
+ })
@@ -0,0 +1,96 @@
1
+ import { withRouteSpec } from "fake-snippets-api/lib/middleware/with-winter-spec"
2
+ import { z } from "zod"
3
+
4
+ export default withRouteSpec({
5
+ methods: ["POST"],
6
+ auth: "none",
7
+ jsonBody: z.object({
8
+ package_release_id: z.string().optional(),
9
+ package_name_with_version: z.string().optional(),
10
+ is_locked: z.boolean().optional(),
11
+ is_latest: z.boolean().optional(),
12
+ license: z.string().optional(),
13
+ }),
14
+ jsonResponse: z.object({
15
+ ok: z.boolean(),
16
+ }),
17
+ })(async (req, ctx) => {
18
+ const {
19
+ package_release_id,
20
+ package_name_with_version,
21
+ is_locked,
22
+ is_latest,
23
+ license,
24
+ } = req.jsonBody
25
+ let releaseId = package_release_id
26
+
27
+ // Handle package_name_with_version lookup
28
+ if (!releaseId && package_name_with_version) {
29
+ const [packageName, version] = package_name_with_version.split("@")
30
+ const pkg = ctx.db.packages.find((p) => p.name === packageName)
31
+ if (pkg) {
32
+ const release = ctx.db.packageReleases.find(
33
+ (pr) => pr.package_id === pkg.package_id && pr.version === version,
34
+ )
35
+ if (release) {
36
+ releaseId = release.package_release_id
37
+ }
38
+ }
39
+ }
40
+
41
+ if (!releaseId) {
42
+ return ctx.error(404, {
43
+ error_code: "package_release_not_found",
44
+ message: "Package release not found",
45
+ })
46
+ }
47
+
48
+ const delta = { is_locked, is_latest, license }
49
+ if (
50
+ Object.keys(delta).filter(
51
+ (k) => delta[k as keyof typeof delta] !== undefined,
52
+ ).length === 0
53
+ ) {
54
+ return ctx.error(400, {
55
+ error_code: "no_fields_provided",
56
+ message: "No fields provided to update",
57
+ })
58
+ }
59
+
60
+ const release = ctx.db.packageReleases.find(
61
+ (pr) => pr.package_release_id === releaseId,
62
+ )
63
+ if (!release) {
64
+ return ctx.error(404, {
65
+ error_code: "package_release_not_found",
66
+ message: "Package release not found",
67
+ })
68
+ }
69
+
70
+ // Handle is_latest updates
71
+ if (is_latest !== undefined && is_latest) {
72
+ ctx.db.packageReleases
73
+ .filter(
74
+ (pr) =>
75
+ pr.package_id === release.package_id &&
76
+ pr.package_release_id !== releaseId &&
77
+ pr.is_latest,
78
+ )
79
+ .forEach((pr) => {
80
+ pr.is_latest = false
81
+ })
82
+ }
83
+
84
+ // Update the release
85
+ Object.assign(release, {
86
+ ...(is_locked !== undefined && { is_locked }),
87
+ ...(is_latest !== undefined && { is_latest }),
88
+ ...(license !== undefined && { license }),
89
+ })
90
+
91
+ ctx.db.updatePackageRelease(release)
92
+
93
+ return ctx.json({
94
+ ok: true,
95
+ })
96
+ })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tscircuit/fake-snippets",
3
- "version": "0.0.4",
3
+ "version": "0.0.5",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -80,6 +80,7 @@
80
80
  "circuit-json-to-bom-csv": "^0.0.6",
81
81
  "circuit-json-to-gerber": "^0.0.16",
82
82
  "circuit-json-to-pnp-csv": "^0.0.6",
83
+ "circuit-json-to-tscircuit": "^0.0.4",
83
84
  "circuit-json-to-readable-netlist": "^0.0.7",
84
85
  "class-variance-authority": "^0.7.0",
85
86
  "clsx": "^2.1.1",
@@ -135,6 +136,7 @@
135
136
  "@types/babel__standalone": "^7.1.7",
136
137
  "@types/bun": "^1.1.10",
137
138
  "@types/country-list": "^2.1.4",
139
+ "@types/node": "^22.13.0",
138
140
  "@types/prismjs": "^1.26.4",
139
141
  "@types/react": "^18.3.9",
140
142
  "@types/react-dom": "^18.3.0",
@@ -0,0 +1,133 @@
1
+ import { test, expect } from "@playwright/test"
2
+ import { exampleCircuitJson } from "./exampleCircuitJson"
3
+
4
+ async function loginToSite(page) {
5
+ const loginButton = page.getByRole("button", { name: "Log in" })
6
+ if (await loginButton.isVisible()) {
7
+ await loginButton.click()
8
+ await page.waitForLoadState("networkidle")
9
+ }
10
+ }
11
+
12
+ test.beforeEach(async ({ page }) => {
13
+ await page.goto("http://127.0.0.1:5177/quickstart")
14
+ await page.waitForTimeout(3000)
15
+ await loginToSite(page).catch(() => {})
16
+ })
17
+
18
+ test("should open and close the Circuit Json Import Dialog", async ({
19
+ page,
20
+ }) => {
21
+ const importButton = page.locator('button:has-text("Import Circuit JSON")')
22
+ await importButton.click()
23
+
24
+ const dialog = page.getByRole("dialog")
25
+ await expect(dialog).toBeVisible()
26
+
27
+ const closeButton = dialog.getByRole("button", { name: "Close" })
28
+ await closeButton.click()
29
+
30
+ await expect(dialog).not.toBeVisible()
31
+ })
32
+
33
+ test("should handle valid Circuit JSON input", async ({ page }) => {
34
+ const importButton = page.getByRole("button", { name: "Import Circuit JSON" })
35
+ await importButton.click()
36
+ const textarea = page.locator(
37
+ 'textarea[placeholder="Paste the Circuit JSON."]',
38
+ )
39
+ await textarea.fill(JSON.stringify(exampleCircuitJson))
40
+
41
+ const importDialogButton = page.getByRole("button", { name: "Import" })
42
+ await importDialogButton.click()
43
+
44
+ const successToast = page.locator(
45
+ 'div.text-sm.font-semibold:has-text("Import Successful")',
46
+ )
47
+ await successToast.waitFor({ state: "visible", timeout: 5000 })
48
+ await expect(successToast).toBeVisible()
49
+ })
50
+
51
+ test("should handle valid Circuit JSON file upload", async ({ page }) => {
52
+ const importButton = page.locator('button:has-text("Import Circuit JSON")')
53
+ await importButton.click()
54
+
55
+ const fileInput = page.locator('input[type="file"]')
56
+
57
+ await fileInput.setInputFiles({
58
+ name: "circuit.json",
59
+ mimeType: "application/json",
60
+ // @ts-expect-error didnt add node types to tsconfig
61
+ buffer: Buffer.from(JSON.stringify(exampleCircuitJson)),
62
+ })
63
+
64
+ const importDialogButton = page.getByRole("button", { name: "Import" })
65
+ await importDialogButton.click()
66
+ const successToast = page.locator(
67
+ 'div.text-sm.font-semibold:has-text("Import Successful")',
68
+ )
69
+ await successToast.waitFor({ state: "visible", timeout: 5000 })
70
+ await expect(successToast).toBeVisible()
71
+ })
72
+
73
+ test("should handle invalid Circuit JSON input", async ({ page }) => {
74
+ const importButton = page.locator('button:has-text("Import Circuit JSON")')
75
+ await importButton.click()
76
+
77
+ const textarea = page.locator(
78
+ 'textarea[placeholder="Paste the Circuit JSON."]',
79
+ )
80
+ await textarea.fill("invalid json content")
81
+
82
+ const importDialogButton = page.getByRole("button", { name: "Import" })
83
+ await importDialogButton.click()
84
+
85
+ const errorToast = page.locator(
86
+ 'div.text-sm.font-semibold:has-text("Invalid Input")',
87
+ )
88
+ await errorToast.waitFor({ state: "visible", timeout: 5000 })
89
+ await expect(errorToast).toBeVisible()
90
+ })
91
+
92
+ test("should handle invalid Circuit JSON file upload", async ({ page }) => {
93
+ const importButton = page.locator('button:has-text("Import Circuit JSON")')
94
+ await importButton.click()
95
+
96
+ const fileInput = page.locator('input[type="file"]')
97
+ await fileInput.setInputFiles({
98
+ name: "circuit.json",
99
+ mimeType: "application/json",
100
+ // @ts-expect-error didnt add node types to tsconfig
101
+ buffer: Buffer.from(JSON.stringify({})),
102
+ })
103
+
104
+ const importDialogButton = page.getByRole("button", { name: "Import" })
105
+ await importDialogButton.click()
106
+
107
+ const errorToast = page.locator(
108
+ 'div.text-sm.font-semibold:has-text("Import Failed")',
109
+ )
110
+ await errorToast.waitFor({ state: "visible", timeout: 5000 })
111
+ await expect(errorToast).toBeVisible()
112
+ })
113
+
114
+ test("should handle non-JSON file upload", async ({ page }) => {
115
+ const importButton = page.locator('button:has-text("Import Circuit JSON")')
116
+ await importButton.click()
117
+
118
+ const fileInput = page.locator('input[type="file"]')
119
+ await fileInput.setInputFiles({
120
+ name: "circuit.txt",
121
+ mimeType: "application/text",
122
+ // @ts-expect-error didnt add node types to tsconfig
123
+ buffer: Buffer.from(""),
124
+ })
125
+
126
+ const importDialogButton = page.getByRole("button", { name: "Import" })
127
+ await importDialogButton.click()
128
+
129
+ const errorToast = page.locator(
130
+ 'div.pb-4 > p:has-text("Please select a valid JSON file.")',
131
+ )
132
+ await expect(errorToast).toBeVisible()
133
+ })