@tscircuit/fake-snippets 0.0.71 → 0.0.73

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.
Files changed (43) hide show
  1. package/bun-tests/fake-snippets-api/routes/package_files/create_or_update.test.ts +26 -0
  2. package/bun.lock +58 -58
  3. package/dist/bundle.js +56 -5
  4. package/dist/index.d.ts +15 -2
  5. package/dist/index.js +47 -1
  6. package/dist/schema.d.ts +8 -0
  7. package/dist/schema.js +1 -0
  8. package/fake-snippets-api/lib/db/db-client.ts +47 -0
  9. package/fake-snippets-api/lib/db/schema.ts +1 -0
  10. package/fake-snippets-api/lib/package_file/generate-fs-sha.ts +20 -0
  11. package/fake-snippets-api/lib/public-mapping/public-map-package.ts +1 -0
  12. package/fake-snippets-api/routes/api/package_files/create.ts +3 -0
  13. package/fake-snippets-api/routes/api/package_files/create_or_update.ts +9 -3
  14. package/fake-snippets-api/routes/api/package_files/delete.ts +3 -0
  15. package/fake-snippets-api/routes/api/packages/create.ts +1 -0
  16. package/package.json +11 -11
  17. package/src/App.tsx +5 -0
  18. package/src/components/FileSidebar.tsx +111 -37
  19. package/src/components/JLCPCBImportDialog.tsx +1 -1
  20. package/src/components/PackageBuildsPage/DeploymentDetailsPage.tsx +56 -0
  21. package/src/components/PackageBuildsPage/build-preview-content.tsx +11 -0
  22. package/src/components/PackageBuildsPage/collapsible-section.tsx +70 -0
  23. package/src/components/PackageBuildsPage/deployment-details-panel.tsx +84 -0
  24. package/src/components/PackageBuildsPage/deployment-header.tsx +75 -0
  25. package/src/components/PackageCard.tsx +1 -10
  26. package/src/components/TrendingPackagesCarousel.tsx +5 -16
  27. package/src/components/ViewPackagePage/components/mobile-sidebar.tsx +1 -1
  28. package/src/components/ViewPackagePage/components/preview-image-squares.tsx +2 -6
  29. package/src/components/package-port/CodeAndPreview.tsx +78 -267
  30. package/src/components/package-port/CodeEditor.tsx +30 -19
  31. package/src/components/package-port/CodeEditorHeader.tsx +7 -6
  32. package/src/components/package-port/EditorNav.tsx +17 -13
  33. package/src/components/ui/tree-view.tsx +3 -3
  34. package/src/hooks/use-current-package-id.ts +2 -8
  35. package/src/hooks/use-preview-images.ts +3 -15
  36. package/src/hooks/useFileManagement.ts +257 -38
  37. package/src/hooks/usePackageFilesLoader.ts +2 -2
  38. package/src/hooks/useUpdatePackageFilesMutation.ts +50 -24
  39. package/src/lib/utils/checkIfManualEditsImported.ts +9 -0
  40. package/src/pages/editor.tsx +2 -10
  41. package/src/pages/package-builds.tsx +33 -0
  42. package/src/pages/package-editor.tsx +2 -14
  43. package/src/hooks/use-get-fsmap-hash-for-package.ts +0 -19
package/dist/index.d.ts CHANGED
@@ -508,6 +508,7 @@ declare const packageSchema: z.ZodObject<{
508
508
  ai_description: z.ZodNullable<z.ZodString>;
509
509
  latest_license: z.ZodOptional<z.ZodNullable<z.ZodString>>;
510
510
  ai_usage_instructions: z.ZodNullable<z.ZodString>;
511
+ latest_package_release_fs_sha: z.ZodDefault<z.ZodNullable<z.ZodString>>;
511
512
  default_view: z.ZodOptional<z.ZodDefault<z.ZodEnum<["files", "3d", "pcb", "schematic"]>>>;
512
513
  }, "strip", z.ZodTypeAny, {
513
514
  name: string;
@@ -535,6 +536,7 @@ declare const packageSchema: z.ZodObject<{
535
536
  website: string | null;
536
537
  ai_description: string | null;
537
538
  ai_usage_instructions: string | null;
539
+ latest_package_release_fs_sha: string | null;
538
540
  snippet_type?: "board" | "package" | "model" | "footprint" | undefined;
539
541
  latest_license?: string | null | undefined;
540
542
  default_view?: "files" | "3d" | "pcb" | "schematic" | undefined;
@@ -566,6 +568,7 @@ declare const packageSchema: z.ZodObject<{
566
568
  is_source_from_github?: boolean | undefined;
567
569
  website?: string | null | undefined;
568
570
  latest_license?: string | null | undefined;
571
+ latest_package_release_fs_sha?: string | null | undefined;
569
572
  default_view?: "files" | "3d" | "pcb" | "schematic" | undefined;
570
573
  }>;
571
574
  type Package = z.infer<typeof packageSchema>;
@@ -779,6 +782,7 @@ declare const createDatabase: ({ seed }?: {
779
782
  website: string | null;
780
783
  ai_description: string | null;
781
784
  ai_usage_instructions: string | null;
785
+ latest_package_release_fs_sha: string | null;
782
786
  snippet_type?: "board" | "package" | "model" | "footprint" | undefined;
783
787
  latest_license?: string | null | undefined;
784
788
  default_view?: "files" | "3d" | "pcb" | "schematic" | undefined;
@@ -889,7 +893,7 @@ declare const createDatabase: ({ seed }?: {
889
893
  }[];
890
894
  total_cost_without_shipping: number;
891
895
  }[];
892
- }, "addOrder" | "getOrderById" | "getOrderFilesByOrderId" | "addOrderQuote" | "getOrderQuoteById" | "getJlcpcbOrderStatesByOrderId" | "getJlcpcbOrderStepRunsByJlcpcbOrderStateId" | "updateOrder" | "addJlcpcbOrderState" | "updateJlcpcbOrderState" | "addOrderFile" | "getOrderFileById" | "addAccount" | "addAccountPackage" | "getAccountPackageById" | "updateAccountPackage" | "deleteAccountPackage" | "addSnippet" | "getLatestSnippets" | "getTrendingSnippets" | "getPackagesByAuthor" | "getSnippetByAuthorAndName" | "updateSnippet" | "getSnippetById" | "searchSnippets" | "searchPackages" | "deleteSnippet" | "addSession" | "getSessions" | "createLoginPage" | "getLoginPage" | "updateLoginPage" | "getAccount" | "updateAccount" | "createSession" | "addStar" | "removeStar" | "hasStarred" | "addPackage" | "updatePackage" | "getPackageById" | "getPackageReleaseById" | "addPackageRelease" | "updatePackageRelease" | "deletePackageFile" | "addPackageFile" | "updatePackageFile" | "getStarCount" | "getPackageFilesByReleaseId"> & {
896
+ }, "addOrder" | "getOrderById" | "getOrderFilesByOrderId" | "addOrderQuote" | "getOrderQuoteById" | "getJlcpcbOrderStatesByOrderId" | "getJlcpcbOrderStepRunsByJlcpcbOrderStateId" | "updateOrder" | "addJlcpcbOrderState" | "updateJlcpcbOrderState" | "addOrderFile" | "getOrderFileById" | "addAccount" | "addAccountPackage" | "getAccountPackageById" | "updateAccountPackage" | "deleteAccountPackage" | "addSnippet" | "getLatestSnippets" | "getTrendingSnippets" | "getPackagesByAuthor" | "getSnippetByAuthorAndName" | "updateSnippet" | "getSnippetById" | "searchSnippets" | "searchPackages" | "deleteSnippet" | "addSession" | "getSessions" | "createLoginPage" | "getLoginPage" | "updateLoginPage" | "getAccount" | "updateAccount" | "createSession" | "addStar" | "removeStar" | "hasStarred" | "addPackage" | "updatePackage" | "getPackageById" | "getPackageReleaseById" | "addPackageRelease" | "updatePackageRelease" | "deletePackageFile" | "addPackageFile" | "updatePackageFile" | "getStarCount" | "getPackageFilesByReleaseId" | "updatePackageReleaseFsSha"> & {
893
897
  addOrder: (order: Omit<Order, "order_id">) => Order;
894
898
  getOrderById: (orderId: string) => Order | undefined;
895
899
  getOrderFilesByOrderId: (orderId: string) => OrderFile[];
@@ -963,6 +967,10 @@ declare const createDatabase: ({ seed }?: {
963
967
  updatePackageFile: (packageFileId: string, updates: Partial<Omit<PackageFile, "package_file_id">>) => PackageFile;
964
968
  getStarCount: (packageId: string) => number;
965
969
  getPackageFilesByReleaseId: (packageReleaseId: string) => PackageFile[];
970
+ /**
971
+ * Update fs_sha for a package release based on its files
972
+ */
973
+ updatePackageReleaseFsSha: (packageReleaseId: string) => void;
966
974
  }> & Omit<{
967
975
  idCounter: number;
968
976
  snippets: {
@@ -1070,6 +1078,7 @@ declare const createDatabase: ({ seed }?: {
1070
1078
  website: string | null;
1071
1079
  ai_description: string | null;
1072
1080
  ai_usage_instructions: string | null;
1081
+ latest_package_release_fs_sha: string | null;
1073
1082
  snippet_type?: "board" | "package" | "model" | "footprint" | undefined;
1074
1083
  latest_license?: string | null | undefined;
1075
1084
  default_view?: "files" | "3d" | "pcb" | "schematic" | undefined;
@@ -1180,7 +1189,7 @@ declare const createDatabase: ({ seed }?: {
1180
1189
  }[];
1181
1190
  total_cost_without_shipping: number;
1182
1191
  }[];
1183
- }, "addOrder" | "getOrderById" | "getOrderFilesByOrderId" | "addOrderQuote" | "getOrderQuoteById" | "getJlcpcbOrderStatesByOrderId" | "getJlcpcbOrderStepRunsByJlcpcbOrderStateId" | "updateOrder" | "addJlcpcbOrderState" | "updateJlcpcbOrderState" | "addOrderFile" | "getOrderFileById" | "addAccount" | "addAccountPackage" | "getAccountPackageById" | "updateAccountPackage" | "deleteAccountPackage" | "addSnippet" | "getLatestSnippets" | "getTrendingSnippets" | "getPackagesByAuthor" | "getSnippetByAuthorAndName" | "updateSnippet" | "getSnippetById" | "searchSnippets" | "searchPackages" | "deleteSnippet" | "addSession" | "getSessions" | "createLoginPage" | "getLoginPage" | "updateLoginPage" | "getAccount" | "updateAccount" | "createSession" | "addStar" | "removeStar" | "hasStarred" | "addPackage" | "updatePackage" | "getPackageById" | "getPackageReleaseById" | "addPackageRelease" | "updatePackageRelease" | "deletePackageFile" | "addPackageFile" | "updatePackageFile" | "getStarCount" | "getPackageFilesByReleaseId"> & {
1192
+ }, "addOrder" | "getOrderById" | "getOrderFilesByOrderId" | "addOrderQuote" | "getOrderQuoteById" | "getJlcpcbOrderStatesByOrderId" | "getJlcpcbOrderStepRunsByJlcpcbOrderStateId" | "updateOrder" | "addJlcpcbOrderState" | "updateJlcpcbOrderState" | "addOrderFile" | "getOrderFileById" | "addAccount" | "addAccountPackage" | "getAccountPackageById" | "updateAccountPackage" | "deleteAccountPackage" | "addSnippet" | "getLatestSnippets" | "getTrendingSnippets" | "getPackagesByAuthor" | "getSnippetByAuthorAndName" | "updateSnippet" | "getSnippetById" | "searchSnippets" | "searchPackages" | "deleteSnippet" | "addSession" | "getSessions" | "createLoginPage" | "getLoginPage" | "updateLoginPage" | "getAccount" | "updateAccount" | "createSession" | "addStar" | "removeStar" | "hasStarred" | "addPackage" | "updatePackage" | "getPackageById" | "getPackageReleaseById" | "addPackageRelease" | "updatePackageRelease" | "deletePackageFile" | "addPackageFile" | "updatePackageFile" | "getStarCount" | "getPackageFilesByReleaseId" | "updatePackageReleaseFsSha"> & {
1184
1193
  addOrder: (order: Omit<Order, "order_id">) => Order;
1185
1194
  getOrderById: (orderId: string) => Order | undefined;
1186
1195
  getOrderFilesByOrderId: (orderId: string) => OrderFile[];
@@ -1254,6 +1263,10 @@ declare const createDatabase: ({ seed }?: {
1254
1263
  updatePackageFile: (packageFileId: string, updates: Partial<Omit<PackageFile, "package_file_id">>) => PackageFile;
1255
1264
  getStarCount: (packageId: string) => number;
1256
1265
  getPackageFilesByReleaseId: (packageReleaseId: string) => PackageFile[];
1266
+ /**
1267
+ * Update fs_sha for a package release based on its files
1268
+ */
1269
+ updatePackageReleaseFsSha: (packageReleaseId: string) => void;
1257
1270
  };
1258
1271
  type DbClient = ReturnType<typeof createDatabase>;
1259
1272
 
package/dist/index.js CHANGED
@@ -188,6 +188,7 @@ var packageSchema = z.object({
188
188
  ai_description: z.string().nullable(),
189
189
  latest_license: z.string().nullable().optional(),
190
190
  ai_usage_instructions: z.string().nullable(),
191
+ latest_package_release_fs_sha: z.string().nullable().default(null),
191
192
  default_view: z.enum(["files", "3d", "pcb", "schematic"]).default("files").optional()
192
193
  });
193
194
  var jlcpcbOrderStateSchema = z.object({
@@ -2001,6 +2002,17 @@ export const SquareWaveModule = () => (
2001
2002
  });
2002
2003
  };
2003
2004
 
2005
+ // fake-snippets-api/lib/package_file/generate-fs-sha.ts
2006
+ import md5 from "md5";
2007
+ function generateFsSha(packageFiles) {
2008
+ const fsMap = {};
2009
+ packageFiles.filter((file) => file.content_text).forEach((file) => {
2010
+ fsMap[file.file_path] = file.content_text || "";
2011
+ });
2012
+ const hash = md5(JSON.stringify(fsMap));
2013
+ return `md5-${hash}`;
2014
+ }
2015
+
2004
2016
  // fake-snippets-api/lib/db/db-client.ts
2005
2017
  var createDatabase = ({ seed: seed2 } = {}) => {
2006
2018
  const db = hoist(createStore(initializer));
@@ -2209,7 +2221,8 @@ var initializer = combine(databaseSchema.parse({}), (set, get) => ({
2209
2221
  is_private: false,
2210
2222
  is_public: true,
2211
2223
  is_unlisted: false,
2212
- latest_package_release_id: `package_release_${nextId}`
2224
+ latest_package_release_id: `package_release_${nextId}`,
2225
+ latest_package_release_fs_sha: null
2213
2226
  };
2214
2227
  const newPackageRelease = {
2215
2228
  package_release_id: `package_release_${nextId}`,
@@ -2265,6 +2278,20 @@ var initializer = combine(databaseSchema.parse({}), (set, get) => ({
2265
2278
  packageFiles: [...state.packageFiles, ...packageFiles],
2266
2279
  idCounter: fileIdCounter
2267
2280
  }));
2281
+ const dbState = get();
2282
+ const releaseFiles = dbState.packageFiles.filter(
2283
+ (pf) => pf.package_release_id === newPackageRelease.package_release_id
2284
+ );
2285
+ const fsSha = generateFsSha(releaseFiles);
2286
+ set((state) => ({
2287
+ ...state,
2288
+ packageReleases: state.packageReleases.map(
2289
+ (pr) => pr.package_release_id === newPackageRelease.package_release_id ? { ...pr, fs_sha: fsSha } : pr
2290
+ ),
2291
+ packages: state.packages.map(
2292
+ (pkg) => pkg.latest_package_release_id === newPackageRelease.package_release_id ? { ...pkg, latest_package_release_fs_sha: fsSha } : pkg
2293
+ )
2294
+ }));
2268
2295
  return {
2269
2296
  snippet_id: newPackage.package_id,
2270
2297
  package_release_id: newPackageRelease.package_release_id,
@@ -2976,6 +3003,25 @@ var initializer = combine(databaseSchema.parse({}), (set, get) => ({
2976
3003
  return state.packageFiles.filter(
2977
3004
  (pf) => pf.package_release_id === packageReleaseId
2978
3005
  );
3006
+ },
3007
+ /**
3008
+ * Update fs_sha for a package release based on its files
3009
+ */
3010
+ updatePackageReleaseFsSha: (packageReleaseId) => {
3011
+ const state = get();
3012
+ const packageFiles = state.packageFiles.filter(
3013
+ (pf) => pf.package_release_id === packageReleaseId
3014
+ );
3015
+ const fsSha = generateFsSha(packageFiles);
3016
+ set((currentState) => ({
3017
+ ...currentState,
3018
+ packageReleases: currentState.packageReleases.map(
3019
+ (pr) => pr.package_release_id === packageReleaseId ? { ...pr, fs_sha: fsSha } : pr
3020
+ ),
3021
+ packages: currentState.packages.map(
3022
+ (pkg) => pkg.latest_package_release_id === packageReleaseId ? { ...pkg, latest_package_release_fs_sha: fsSha } : pkg
3023
+ )
3024
+ }));
2979
3025
  }
2980
3026
  }));
2981
3027
 
package/dist/schema.d.ts CHANGED
@@ -633,6 +633,7 @@ declare const packageSchema: z.ZodObject<{
633
633
  ai_description: z.ZodNullable<z.ZodString>;
634
634
  latest_license: z.ZodOptional<z.ZodNullable<z.ZodString>>;
635
635
  ai_usage_instructions: z.ZodNullable<z.ZodString>;
636
+ latest_package_release_fs_sha: z.ZodDefault<z.ZodNullable<z.ZodString>>;
636
637
  default_view: z.ZodOptional<z.ZodDefault<z.ZodEnum<["files", "3d", "pcb", "schematic"]>>>;
637
638
  }, "strip", z.ZodTypeAny, {
638
639
  name: string;
@@ -660,6 +661,7 @@ declare const packageSchema: z.ZodObject<{
660
661
  website: string | null;
661
662
  ai_description: string | null;
662
663
  ai_usage_instructions: string | null;
664
+ latest_package_release_fs_sha: string | null;
663
665
  snippet_type?: "board" | "package" | "model" | "footprint" | undefined;
664
666
  latest_license?: string | null | undefined;
665
667
  default_view?: "files" | "3d" | "pcb" | "schematic" | undefined;
@@ -691,6 +693,7 @@ declare const packageSchema: z.ZodObject<{
691
693
  is_source_from_github?: boolean | undefined;
692
694
  website?: string | null | undefined;
693
695
  latest_license?: string | null | undefined;
696
+ latest_package_release_fs_sha?: string | null | undefined;
694
697
  default_view?: "files" | "3d" | "pcb" | "schematic" | undefined;
695
698
  }>;
696
699
  type Package = z.infer<typeof packageSchema>;
@@ -1071,6 +1074,7 @@ declare const databaseSchema: z.ZodObject<{
1071
1074
  ai_description: z.ZodNullable<z.ZodString>;
1072
1075
  latest_license: z.ZodOptional<z.ZodNullable<z.ZodString>>;
1073
1076
  ai_usage_instructions: z.ZodNullable<z.ZodString>;
1077
+ latest_package_release_fs_sha: z.ZodDefault<z.ZodNullable<z.ZodString>>;
1074
1078
  default_view: z.ZodOptional<z.ZodDefault<z.ZodEnum<["files", "3d", "pcb", "schematic"]>>>;
1075
1079
  }, "strip", z.ZodTypeAny, {
1076
1080
  name: string;
@@ -1098,6 +1102,7 @@ declare const databaseSchema: z.ZodObject<{
1098
1102
  website: string | null;
1099
1103
  ai_description: string | null;
1100
1104
  ai_usage_instructions: string | null;
1105
+ latest_package_release_fs_sha: string | null;
1101
1106
  snippet_type?: "board" | "package" | "model" | "footprint" | undefined;
1102
1107
  latest_license?: string | null | undefined;
1103
1108
  default_view?: "files" | "3d" | "pcb" | "schematic" | undefined;
@@ -1129,6 +1134,7 @@ declare const databaseSchema: z.ZodObject<{
1129
1134
  is_source_from_github?: boolean | undefined;
1130
1135
  website?: string | null | undefined;
1131
1136
  latest_license?: string | null | undefined;
1137
+ latest_package_release_fs_sha?: string | null | undefined;
1132
1138
  default_view?: "files" | "3d" | "pcb" | "schematic" | undefined;
1133
1139
  }>, "many">>;
1134
1140
  orders: z.ZodDefault<z.ZodArray<z.ZodObject<{
@@ -1576,6 +1582,7 @@ declare const databaseSchema: z.ZodObject<{
1576
1582
  website: string | null;
1577
1583
  ai_description: string | null;
1578
1584
  ai_usage_instructions: string | null;
1585
+ latest_package_release_fs_sha: string | null;
1579
1586
  snippet_type?: "board" | "package" | "model" | "footprint" | undefined;
1580
1587
  latest_license?: string | null | undefined;
1581
1588
  default_view?: "files" | "3d" | "pcb" | "schematic" | undefined;
@@ -1795,6 +1802,7 @@ declare const databaseSchema: z.ZodObject<{
1795
1802
  is_source_from_github?: boolean | undefined;
1796
1803
  website?: string | null | undefined;
1797
1804
  latest_license?: string | null | undefined;
1805
+ latest_package_release_fs_sha?: string | null | undefined;
1798
1806
  default_view?: "files" | "3d" | "pcb" | "schematic" | undefined;
1799
1807
  }[] | undefined;
1800
1808
  orders?: {
package/dist/schema.js CHANGED
@@ -183,6 +183,7 @@ var packageSchema = z.object({
183
183
  ai_description: z.string().nullable(),
184
184
  latest_license: z.string().nullable().optional(),
185
185
  ai_usage_instructions: z.string().nullable(),
186
+ latest_package_release_fs_sha: z.string().nullable().default(null),
186
187
  default_view: z.enum(["files", "3d", "pcb", "schematic"]).default("files").optional()
187
188
  });
188
189
  var jlcpcbOrderStateSchema = z.object({
@@ -22,6 +22,7 @@ import {
22
22
  type snippetSchema,
23
23
  } from "./schema.ts"
24
24
  import { seed as seedFn } from "./seed"
25
+ import { generateFsSha } from "../package_file/generate-fs-sha"
25
26
 
26
27
  export const createDatabase = ({ seed }: { seed?: boolean } = {}) => {
27
28
  const db = hoist(createStore(initializer))
@@ -271,6 +272,7 @@ const initializer = combine(databaseSchema.parse({}), (set, get) => ({
271
272
  is_public: true,
272
273
  is_unlisted: false,
273
274
  latest_package_release_id: `package_release_${nextId}`,
275
+ latest_package_release_fs_sha: null,
274
276
  }
275
277
 
276
278
  // Create package release
@@ -341,6 +343,27 @@ const initializer = combine(databaseSchema.parse({}), (set, get) => ({
341
343
  idCounter: fileIdCounter,
342
344
  }))
343
345
 
346
+ // Update fs_sha for the new package release
347
+ const dbState = get()
348
+ const releaseFiles = dbState.packageFiles.filter(
349
+ (pf) => pf.package_release_id === newPackageRelease.package_release_id,
350
+ )
351
+ const fsSha = generateFsSha(releaseFiles)
352
+
353
+ set((state) => ({
354
+ ...state,
355
+ packageReleases: state.packageReleases.map((pr) =>
356
+ pr.package_release_id === newPackageRelease.package_release_id
357
+ ? { ...pr, fs_sha: fsSha }
358
+ : pr,
359
+ ),
360
+ packages: state.packages.map((pkg) =>
361
+ pkg.latest_package_release_id === newPackageRelease.package_release_id
362
+ ? { ...pkg, latest_package_release_fs_sha: fsSha }
363
+ : pkg,
364
+ ),
365
+ }))
366
+
344
367
  // Return in the same format as create endpoint
345
368
  return {
346
369
  snippet_id: newPackage.package_id,
@@ -1287,4 +1310,28 @@ const initializer = combine(databaseSchema.parse({}), (set, get) => ({
1287
1310
  (pf) => pf.package_release_id === packageReleaseId,
1288
1311
  )
1289
1312
  },
1313
+ /**
1314
+ * Update fs_sha for a package release based on its files
1315
+ */
1316
+ updatePackageReleaseFsSha: (packageReleaseId: string): void => {
1317
+ const state = get()
1318
+ const packageFiles = state.packageFiles.filter(
1319
+ (pf) => pf.package_release_id === packageReleaseId,
1320
+ )
1321
+ const fsSha = generateFsSha(packageFiles)
1322
+
1323
+ set((currentState) => ({
1324
+ ...currentState,
1325
+ packageReleases: currentState.packageReleases.map((pr) =>
1326
+ pr.package_release_id === packageReleaseId
1327
+ ? { ...pr, fs_sha: fsSha }
1328
+ : pr,
1329
+ ),
1330
+ packages: currentState.packages.map((pkg) =>
1331
+ pkg.latest_package_release_id === packageReleaseId
1332
+ ? { ...pkg, latest_package_release_fs_sha: fsSha }
1333
+ : pkg,
1334
+ ),
1335
+ }))
1336
+ },
1290
1337
  }))
@@ -219,6 +219,7 @@ export const packageSchema = z.object({
219
219
  ai_description: z.string().nullable(),
220
220
  latest_license: z.string().nullable().optional(),
221
221
  ai_usage_instructions: z.string().nullable(),
222
+ latest_package_release_fs_sha: z.string().nullable().default(null),
222
223
  default_view: z
223
224
  .enum(["files", "3d", "pcb", "schematic"])
224
225
  .default("files")
@@ -0,0 +1,20 @@
1
+ import md5 from "md5"
2
+ import type { PackageFile } from "../db/schema"
3
+
4
+ /**
5
+ * Generate an MD5 hash from package files content
6
+ * Uses the same format as existing tests: creates a map of file_path -> content_text
7
+ * and generates an MD5 hash of the JSON stringified map
8
+ */
9
+ export function generateFsSha(packageFiles: PackageFile[]): string {
10
+ const fsMap: Record<string, string> = {}
11
+
12
+ packageFiles
13
+ .filter((file) => file.content_text) // Only include files with content
14
+ .forEach((file) => {
15
+ fsMap[file.file_path] = file.content_text || ""
16
+ })
17
+
18
+ const hash = md5(JSON.stringify(fsMap))
19
+ return `md5-${hash}`
20
+ }
@@ -25,6 +25,7 @@ export const publicMapPackage = (internalPackage: {
25
25
  is_footprint: boolean
26
26
  is_private: boolean | null
27
27
  is_unlisted: boolean | null
28
+ latest_package_release_fs_sha: string | null
28
29
  }): zt.Package => {
29
30
  return {
30
31
  ...internalPackage,
@@ -125,6 +125,9 @@ export default withRouteSpec(routeSpec)(async (req, ctx) => {
125
125
  // Add to the test database
126
126
  ctx.db.addPackageFile(newPackageFile)
127
127
 
128
+ // Update fs_sha for the package release
129
+ ctx.db.updatePackageReleaseFsSha(packageReleaseId)
130
+
128
131
  return ctx.json({
129
132
  ok: true,
130
133
  package_file: newPackageFile,
@@ -28,7 +28,7 @@ const routeSpec = {
28
28
  }, "Cannot specify both package_release_id and package_name_with_version")
29
29
  .refine((v) => {
30
30
  if (v.content_base64 && v.content_text) return false
31
- if (!v.content_base64 && !v.content_text) return false
31
+ if (!v.content_base64 && v.content_text === undefined) return false
32
32
  return true
33
33
  }, "Either content_base64 or content_text is required"),
34
34
  jsonResponse: z.object({
@@ -136,7 +136,7 @@ export default withRouteSpec(routeSpec)(async (req, ctx) => {
136
136
  exisitingFile.package_file_id,
137
137
  {
138
138
  content_text:
139
- content_text ||
139
+ content_text ??
140
140
  (content_base64
141
141
  ? Buffer.from(content_base64, "base64").toString()
142
142
  : null),
@@ -146,6 +146,9 @@ export default withRouteSpec(routeSpec)(async (req, ctx) => {
146
146
  },
147
147
  )
148
148
 
149
+ // Update fs_sha for the package release
150
+ ctx.db.updatePackageReleaseFsSha(packageReleaseId)
151
+
149
152
  return ctx.json({
150
153
  ok: true,
151
154
  package_file,
@@ -167,7 +170,7 @@ export default withRouteSpec(routeSpec)(async (req, ctx) => {
167
170
  package_release_id: packageReleaseId,
168
171
  file_path,
169
172
  content_text:
170
- content_text ||
173
+ content_text ??
171
174
  (content_base64
172
175
  ? Buffer.from(content_base64, "base64").toString()
173
176
  : null),
@@ -181,6 +184,9 @@ export default withRouteSpec(routeSpec)(async (req, ctx) => {
181
184
  // Add to the test database
182
185
  ctx.db.addPackageFile(newPackageFile)
183
186
 
187
+ // Update fs_sha for the package release
188
+ ctx.db.updatePackageReleaseFsSha(packageReleaseId)
189
+
184
190
  return ctx.json({
185
191
  ok: true,
186
192
  package_file: newPackageFile,
@@ -100,6 +100,9 @@ export default withRouteSpec({
100
100
  })
101
101
  }
102
102
 
103
+ // Update fs_sha for the package release
104
+ ctx.db.updatePackageReleaseFsSha(packageReleaseId)
105
+
103
106
  return ctx.json({
104
107
  ok: true,
105
108
  })
@@ -42,6 +42,7 @@ export default withRouteSpec({
42
42
  owner_org_id: ctx.auth.personal_org_id,
43
43
  owner_github_username: ctx.auth.github_username,
44
44
  latest_package_release_id: null,
45
+ latest_package_release_fs_sha: null,
45
46
  latest_version: null,
46
47
  license: null,
47
48
  website: null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tscircuit/fake-snippets",
3
- "version": "0.0.71",
3
+ "version": "0.0.73",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -26,7 +26,7 @@
26
26
  "lint": "biome format .",
27
27
  "build:vite:analyze": "VITE_BUNDLE_ANALYZE=true vite build",
28
28
  "build:fake-api:tsup": "tsup-node ./fake-snippets-api/lib/index.ts --format esm --dts",
29
- "build:fake-api:bundle": "winterspec bundle -o dist/bundle.js",
29
+ "build:fake-api:bundle": "winterspec2 bundle -o dist/bundle.js",
30
30
  "build:fake-api": "bun run build:fake-api:tsup && bun run build:fake-api:bundle && bun run build:fake-api:schema",
31
31
  "build:fake-api:schema": "tsup-node ./fake-snippets-api/lib/db/schema.ts --format esm --dts",
32
32
  "generate-images": "bun run scripts/generate-image-sizes.ts",
@@ -71,11 +71,11 @@
71
71
  "@radix-ui/react-toggle-group": "^1.1.0",
72
72
  "@radix-ui/react-tooltip": "^1.1.2",
73
73
  "@tscircuit/eval": "^0.0.198",
74
- "@tscircuit/footprinter": "^0.0.124",
74
+ "@tscircuit/footprinter": "^0.0.169",
75
75
  "@tscircuit/layout": "^0.0.29",
76
76
  "@tscircuit/math-utils": "^0.0.10",
77
77
  "@tscircuit/mm": "^0.0.8",
78
- "@tscircuit/props": "^0.0.186",
78
+ "@tscircuit/props": "^0.0.194",
79
79
  "@types/file-saver": "^2.0.7",
80
80
  "@types/ms": "^0.7.34",
81
81
  "@typescript/ata": "^0.9.7",
@@ -83,7 +83,7 @@
83
83
  "@valtown/codemirror-ts": "^2.2.0",
84
84
  "@vercel/analytics": "^1.4.1",
85
85
  "change-case": "^5.4.4",
86
- "circuit-json": "^0.0.164",
86
+ "circuit-json": "^0.0.190",
87
87
  "circuit-json-to-bom-csv": "^0.0.6",
88
88
  "circuit-json-to-gerber": "^0.0.21",
89
89
  "circuit-json-to-pnp-csv": "^0.0.6",
@@ -127,7 +127,7 @@
127
127
  "recharts": "^2.12.7",
128
128
  "remark-gfm": "^4.0.1",
129
129
  "rollup-plugin-visualizer": "^5.12.0",
130
- "schematic-symbols": "^0.0.132",
130
+ "schematic-symbols": "^0.0.140",
131
131
  "sitemap": "^8.0.0",
132
132
  "sonner": "^1.5.0",
133
133
  "states-us": "^1.1.1",
@@ -145,9 +145,9 @@
145
145
  "@biomejs/biome": "^1.9.2",
146
146
  "@playwright/test": "^1.48.0",
147
147
  "@tailwindcss/typography": "^0.5.16",
148
- "@tscircuit/core": "^0.0.384",
148
+ "@tscircuit/core": "^0.0.433",
149
149
  "@tscircuit/prompt-benchmarks": "^0.0.28",
150
- "@tscircuit/runframe": "^0.0.494",
150
+ "@tscircuit/runframe": "^0.0.507",
151
151
  "@types/babel__standalone": "^7.1.7",
152
152
  "@types/bun": "^1.1.10",
153
153
  "@types/country-list": "^2.1.4",
@@ -161,7 +161,7 @@
161
161
  "@typescript/vfs": "^1.6.0",
162
162
  "@vitejs/plugin-react": "^4.3.1",
163
163
  "autoprefixer": "^10.4.20",
164
- "circuit-to-svg": "^0.0.101",
164
+ "circuit-to-svg": "^0.0.136",
165
165
  "get-port": "^7.1.0",
166
166
  "globals": "^15.9.0",
167
167
  "he": "^1.2.0",
@@ -175,11 +175,11 @@
175
175
  "shiki": "^3.2.1",
176
176
  "tailwindcss": "^3.4.13",
177
177
  "terser": "^5.27.0",
178
- "tsup": "^8.3.5",
178
+ "tsup": "^8.5.0",
179
179
  "typescript": "^5.6.3",
180
180
  "vite": "^6.3.4",
181
181
  "vite-plugin-image-optimizer": "^1.1.8",
182
- "winterspec": "^0.0.94",
182
+ "winterspec": "^0.0.107",
183
183
  "zod": "^3.23.8",
184
184
  "zustand": "^4.5.5",
185
185
  "zustand-hoist": "^2.0.1"
package/src/App.tsx CHANGED
@@ -68,6 +68,7 @@ const UserProfilePage = lazyImport(() => import("@/pages/user-profile"))
68
68
  const DevLoginPage = lazyImport(() => import("@/pages/dev-login"))
69
69
  const BetaPage = lazyImport(() => import("@/pages/beta"))
70
70
  const ViewPackagePage = lazyImport(() => import("@/pages/view-package"))
71
+ const PackageBuildsPage = lazyImport(() => import("@/pages/package-builds"))
71
72
  const TrendingPage = lazyImport(() => import("@/pages/trending"))
72
73
  const PackageEditorPage = lazyImport(async () => {
73
74
  const [editorModule] = await Promise.all([
@@ -124,6 +125,10 @@ function App() {
124
125
  <Route path="/dev-login" component={DevLoginPage} />
125
126
  <Route path="/:username" component={UserProfilePage} />
126
127
  <Route path="/:author/:packageName" component={ViewPackagePage} />
128
+ <Route
129
+ path="/:author/:packageName/builds"
130
+ component={PackageBuildsPage}
131
+ />
127
132
  <Route component={lazyImport(() => import("@/pages/404"))} />
128
133
  </Switch>
129
134
  </Suspense>