d1s 0.2.9 → 0.2.11

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 (217) hide show
  1. package/dist/.next/.next/BUILD_ID +1 -1
  2. package/dist/.next/.next/app-build-manifest.json +28 -28
  3. package/dist/.next/.next/app-path-routes-manifest.json +1 -1
  4. package/dist/.next/.next/build-manifest.json +3 -3
  5. package/dist/.next/.next/cache/.rscinfo +1 -1
  6. package/dist/.next/.next/cache/.tsbuildinfo +1 -1
  7. package/dist/.next/.next/cache/webpack/client-production/0.pack +0 -0
  8. package/dist/.next/.next/cache/webpack/client-production/1.pack +0 -0
  9. package/dist/.next/.next/cache/webpack/client-production/2.pack +0 -0
  10. package/dist/.next/.next/cache/webpack/client-production/3.pack +0 -0
  11. package/dist/.next/.next/cache/webpack/client-production/index.pack +0 -0
  12. package/dist/.next/.next/cache/webpack/client-production/index.pack.old +0 -0
  13. package/dist/.next/.next/cache/webpack/edge-server-production/index.pack +0 -0
  14. package/dist/.next/.next/cache/webpack/server-production/0.pack +0 -0
  15. package/dist/.next/.next/cache/webpack/server-production/1.pack +0 -0
  16. package/dist/.next/.next/cache/webpack/server-production/2.pack +0 -0
  17. package/dist/.next/.next/cache/webpack/server-production/index.pack +0 -0
  18. package/dist/.next/.next/cache/webpack/server-production/index.pack.old +0 -0
  19. package/dist/.next/.next/next-minimal-server.js.nft.json +1 -1
  20. package/dist/.next/.next/next-server.js.nft.json +1 -1
  21. package/dist/.next/.next/prerender-manifest.json +1 -1
  22. package/dist/.next/.next/required-server-files.json +1 -1
  23. package/dist/.next/.next/server/app/_not-found/page.js +1 -1
  24. package/dist/.next/.next/server/app/_not-found/page.js.nft.json +1 -1
  25. package/dist/.next/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  26. package/dist/.next/.next/server/app/_not-found.html +1 -1
  27. package/dist/.next/.next/server/app/_not-found.rsc +4 -4
  28. package/dist/.next/.next/server/app/api/cloudflare/route.js +1 -1
  29. package/dist/.next/.next/server/app/api/cloudflare/route_client-reference-manifest.js +1 -1
  30. package/dist/.next/.next/server/app/api/databases/route.js +1 -1
  31. package/dist/.next/.next/server/app/api/databases/route_client-reference-manifest.js +1 -1
  32. package/dist/.next/.next/server/app/api/migrations/sync/route.js +3 -3
  33. package/dist/.next/.next/server/app/api/migrations/sync/route_client-reference-manifest.js +1 -1
  34. package/dist/.next/.next/server/app/api/migrations/sync-files/route.js +8 -8
  35. package/dist/.next/.next/server/app/api/migrations/sync-files/route_client-reference-manifest.js +1 -1
  36. package/dist/.next/.next/server/app/api/pull-data/confirm/route.js +1 -1
  37. package/dist/.next/.next/server/app/api/pull-data/confirm/route_client-reference-manifest.js +1 -1
  38. package/dist/.next/.next/server/app/api/pull-remote/confirm/route.js +2 -2
  39. package/dist/.next/.next/server/app/api/pull-remote/confirm/route_client-reference-manifest.js +1 -1
  40. package/dist/.next/.next/server/app/api/pull-remote/preview/route.js +2 -2
  41. package/dist/.next/.next/server/app/api/pull-remote/preview/route_client-reference-manifest.js +1 -1
  42. package/dist/.next/.next/server/app/api/push-remote/confirm/route.js +5 -5
  43. package/dist/.next/.next/server/app/api/push-remote/confirm/route_client-reference-manifest.js +1 -1
  44. package/dist/.next/.next/server/app/api/push-remote/preview/route.js +3 -3
  45. package/dist/.next/.next/server/app/api/push-remote/preview/route_client-reference-manifest.js +1 -1
  46. package/dist/.next/.next/server/app/api/query/route.js +3 -3
  47. package/dist/.next/.next/server/app/api/query/route_client-reference-manifest.js +1 -1
  48. package/dist/.next/.next/server/app/api/tables/[name]/route.js +2 -2
  49. package/dist/.next/.next/server/app/api/tables/[name]/route_client-reference-manifest.js +1 -1
  50. package/dist/.next/.next/server/app/api/tables/route.js +2 -2
  51. package/dist/.next/.next/server/app/api/tables/route_client-reference-manifest.js +1 -1
  52. package/dist/.next/.next/server/app/api/update/route.js +1 -5
  53. package/dist/.next/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  54. package/dist/.next/.next/server/app/index.html +3 -3
  55. package/dist/.next/.next/server/app/index.rsc +5 -5
  56. package/dist/.next/.next/server/app/page.js +11 -13
  57. package/dist/.next/.next/server/app/page.js.nft.json +1 -1
  58. package/dist/.next/.next/server/app/page_client-reference-manifest.js +1 -1
  59. package/dist/.next/.next/server/app-paths-manifest.json +1 -1
  60. package/dist/.next/.next/server/chunks/226.js +6 -6
  61. package/dist/.next/.next/server/chunks/391.js +5 -5
  62. package/dist/.next/.next/server/chunks/452.js +1 -1
  63. package/dist/.next/.next/server/chunks/627.js +3 -3
  64. package/dist/.next/.next/server/chunks/638.js +3 -3
  65. package/dist/.next/.next/server/chunks/90.js +1 -0
  66. package/dist/.next/.next/server/chunks/992.js +3 -3
  67. package/dist/.next/.next/server/middleware-build-manifest.js +1 -1
  68. package/dist/.next/.next/server/next-font-manifest.js +1 -1
  69. package/dist/.next/.next/server/next-font-manifest.json +1 -1
  70. package/dist/.next/.next/server/pages/404.html +1 -1
  71. package/dist/.next/.next/server/pages/500.html +1 -1
  72. package/dist/.next/.next/server/pages/_app.js +1 -1
  73. package/dist/.next/.next/server/pages/_document.js +1 -1
  74. package/dist/.next/.next/server/pages/_error.js +1 -1
  75. package/dist/.next/.next/server/server-reference-manifest.json +1 -1
  76. package/dist/.next/.next/static/bHYW7fgIBDF8-Q33_WPoE/_buildManifest.js +1 -0
  77. package/dist/.next/.next/static/bHYW7fgIBDF8-Q33_WPoE/_ssgManifest.js +1 -0
  78. package/dist/.next/.next/static/chunks/576-c4daf274bb6469a1.js +1 -0
  79. package/dist/.next/.next/static/chunks/app/layout-be6ee67cf0c92654.js +1 -0
  80. package/dist/.next/.next/static/chunks/app/page-36ed30b2266617f3.js +1 -0
  81. package/dist/.next/.next/static/chunks/{main-app-d36cdfb402b40f13.js → main-app-fa35024de9bd9284.js} +1 -1
  82. package/dist/.next/.next/static/css/9f6c25c5400f83d3.css +3 -0
  83. package/dist/.next/.next/trace +3 -3
  84. package/dist/.next/.next/types/app/api/cloudflare/route.ts +1 -1
  85. package/dist/.next/.next/types/app/api/databases/route.ts +1 -1
  86. package/dist/.next/.next/types/app/api/migrations/sync/route.ts +1 -1
  87. package/dist/.next/.next/types/app/api/migrations/sync-files/route.ts +1 -1
  88. package/dist/.next/.next/types/app/api/pull-data/confirm/route.ts +1 -1
  89. package/dist/.next/.next/types/app/api/pull-remote/confirm/route.ts +1 -1
  90. package/dist/.next/.next/types/app/api/pull-remote/preview/route.ts +1 -1
  91. package/dist/.next/.next/types/app/api/push-remote/confirm/route.ts +1 -1
  92. package/dist/.next/.next/types/app/api/push-remote/preview/route.ts +1 -1
  93. package/dist/.next/.next/types/app/api/query/route.ts +1 -1
  94. package/dist/.next/.next/types/app/api/tables/[name]/route.ts +1 -1
  95. package/dist/.next/.next/types/app/api/tables/route.ts +1 -1
  96. package/dist/.next/.next/types/app/api/update/route.ts +1 -1
  97. package/dist/.next/.next/types/app/page.ts +1 -1
  98. package/dist/.next/BUILD_ID +1 -1
  99. package/dist/.next/app-build-manifest.json +41 -41
  100. package/dist/.next/app-path-routes-manifest.json +1 -1
  101. package/dist/.next/build-manifest.json +3 -3
  102. package/dist/.next/cache/.rscinfo +1 -1
  103. package/dist/.next/cache/.tsbuildinfo +1 -1
  104. package/dist/.next/cache/webpack/client-production/0.pack +0 -0
  105. package/dist/.next/cache/webpack/client-production/index.pack +0 -0
  106. package/dist/.next/cache/webpack/edge-server-production/index.pack +0 -0
  107. package/dist/.next/cache/webpack/server-production/0.pack +0 -0
  108. package/dist/.next/cache/webpack/server-production/index.pack +0 -0
  109. package/dist/.next/next-minimal-server.js.nft.json +1 -1
  110. package/dist/.next/next-server.js.nft.json +1 -1
  111. package/dist/.next/prerender-manifest.json +1 -1
  112. package/dist/.next/required-server-files.json +1 -1
  113. package/dist/.next/server/app/_not-found/page.js +1 -1
  114. package/dist/.next/server/app/_not-found/page.js.nft.json +1 -1
  115. package/dist/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  116. package/dist/.next/server/app/_not-found.html +1 -1
  117. package/dist/.next/server/app/_not-found.rsc +4 -4
  118. package/dist/.next/server/app/api/cloudflare/route.js +1 -1
  119. package/dist/.next/server/app/api/cloudflare/route_client-reference-manifest.js +1 -1
  120. package/dist/.next/server/app/api/databases/route.js +1 -1
  121. package/dist/.next/server/app/api/databases/route_client-reference-manifest.js +1 -1
  122. package/dist/.next/server/app/api/migrations/sync/route.js +3 -3
  123. package/dist/.next/server/app/api/migrations/sync/route_client-reference-manifest.js +1 -1
  124. package/dist/.next/server/app/api/migrations/sync-files/route.js +8 -8
  125. package/dist/.next/server/app/api/migrations/sync-files/route_client-reference-manifest.js +1 -1
  126. package/dist/.next/server/app/api/pull-data/confirm/route.js +1 -1
  127. package/dist/.next/server/app/api/pull-data/confirm/route_client-reference-manifest.js +1 -1
  128. package/dist/.next/server/app/api/pull-remote/confirm/route.js +2 -2
  129. package/dist/.next/server/app/api/pull-remote/confirm/route_client-reference-manifest.js +1 -1
  130. package/dist/.next/server/app/api/pull-remote/preview/route.js +2 -2
  131. package/dist/.next/server/app/api/pull-remote/preview/route_client-reference-manifest.js +1 -1
  132. package/dist/.next/server/app/api/push-remote/confirm/route.js +5 -5
  133. package/dist/.next/server/app/api/push-remote/confirm/route_client-reference-manifest.js +1 -1
  134. package/dist/.next/server/app/api/push-remote/preview/route.js +3 -3
  135. package/dist/.next/server/app/api/push-remote/preview/route_client-reference-manifest.js +1 -1
  136. package/dist/.next/server/app/api/query/route.js +3 -3
  137. package/dist/.next/server/app/api/query/route_client-reference-manifest.js +1 -1
  138. package/dist/.next/server/app/api/tables/[name]/route.js +2 -2
  139. package/dist/.next/server/app/api/tables/[name]/route_client-reference-manifest.js +1 -1
  140. package/dist/.next/server/app/api/tables/route.js +2 -2
  141. package/dist/.next/server/app/api/tables/route_client-reference-manifest.js +1 -1
  142. package/dist/.next/server/app/api/update/route.js +1 -5
  143. package/dist/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  144. package/dist/.next/server/app/index.html +3 -3
  145. package/dist/.next/server/app/index.rsc +5 -5
  146. package/dist/.next/server/app/page.js +11 -13
  147. package/dist/.next/server/app/page.js.nft.json +1 -1
  148. package/dist/.next/server/app/page_client-reference-manifest.js +1 -1
  149. package/dist/.next/server/app-paths-manifest.json +5 -5
  150. package/dist/.next/server/chunks/226.js +6 -6
  151. package/dist/.next/server/chunks/391.js +5 -5
  152. package/dist/.next/server/chunks/452.js +1 -1
  153. package/dist/.next/server/chunks/627.js +3 -3
  154. package/dist/.next/server/chunks/638.js +3 -3
  155. package/dist/.next/server/chunks/90.js +1 -0
  156. package/dist/.next/server/chunks/992.js +3 -3
  157. package/dist/.next/server/middleware-build-manifest.js +1 -1
  158. package/dist/.next/server/next-font-manifest.js +1 -1
  159. package/dist/.next/server/next-font-manifest.json +1 -1
  160. package/dist/.next/server/pages/404.html +1 -1
  161. package/dist/.next/server/pages/500.html +1 -1
  162. package/dist/.next/server/pages/_app.js +1 -1
  163. package/dist/.next/server/pages/_document.js +1 -1
  164. package/dist/.next/server/pages/_error.js +1 -1
  165. package/dist/.next/server/server-reference-manifest.json +1 -1
  166. package/dist/.next/static/ZBTCAyp1kO5FdEHMU-Dw2/_buildManifest.js +1 -0
  167. package/dist/.next/static/ZBTCAyp1kO5FdEHMU-Dw2/_ssgManifest.js +1 -0
  168. package/dist/.next/static/chunks/576-c4daf274bb6469a1.js +1 -0
  169. package/dist/.next/static/chunks/app/layout-be6ee67cf0c92654.js +1 -0
  170. package/dist/.next/static/chunks/app/page-36ed30b2266617f3.js +1 -0
  171. package/dist/.next/static/chunks/{main-app-d36cdfb402b40f13.js → main-app-fa35024de9bd9284.js} +1 -1
  172. package/dist/.next/static/css/9f6c25c5400f83d3.css +3 -0
  173. package/dist/.next/trace +14 -17
  174. package/dist/.next/types/app/api/cloudflare/route.ts +1 -1
  175. package/dist/.next/types/app/api/databases/route.ts +1 -1
  176. package/dist/.next/types/app/api/migrations/sync/route.ts +1 -1
  177. package/dist/.next/types/app/api/migrations/sync-files/route.ts +1 -1
  178. package/dist/.next/types/app/api/pull-data/confirm/route.ts +1 -1
  179. package/dist/.next/types/app/api/pull-remote/confirm/route.ts +1 -1
  180. package/dist/.next/types/app/api/pull-remote/preview/route.ts +1 -1
  181. package/dist/.next/types/app/api/push-remote/confirm/route.ts +1 -1
  182. package/dist/.next/types/app/api/push-remote/preview/route.ts +1 -1
  183. package/dist/.next/types/app/api/query/route.ts +1 -1
  184. package/dist/.next/types/app/api/tables/[name]/route.ts +1 -1
  185. package/dist/.next/types/app/api/tables/route.ts +1 -1
  186. package/dist/.next/types/app/api/update/route.ts +1 -1
  187. package/dist/.next/types/app/page.ts +1 -1
  188. package/dist/app/api/update/route.js +29 -20
  189. package/dist/app/app/api/update/route.js +29 -20
  190. package/dist/app/app/page.jsx +13 -23
  191. package/dist/app/page.jsx +13 -23
  192. package/dist/d1-studio.js +30 -194
  193. package/dist/lib/sql-operations.js +30 -24
  194. package/dist/lib/version-utils.js +34 -0
  195. package/package.json +2 -2
  196. package/scripts/studio.js +30 -194
  197. package/src/app/api/update/route.js +29 -20
  198. package/src/app/page.jsx +13 -23
  199. package/src/components/sidebar.jsx +2 -1
  200. package/src/components/table-view.jsx +1 -1
  201. package/src/components/update-banner.jsx +69 -38
  202. package/src/lib/sql-operations.js +30 -24
  203. package/src/lib/version-utils.js +34 -0
  204. package/dist/.next/.next/server/chunks/336.js +0 -1
  205. package/dist/.next/.next/static/chunks/176-9146975ef8699719.js +0 -1
  206. package/dist/.next/.next/static/chunks/app/layout-8360fab69b848283.js +0 -1
  207. package/dist/.next/.next/static/chunks/app/page-5990cb23b8bd27c4.js +0 -1
  208. package/dist/.next/.next/static/css/674bee6608c638e4.css +0 -3
  209. package/dist/.next/server/chunks/336.js +0 -1
  210. package/dist/.next/static/chunks/176-9146975ef8699719.js +0 -1
  211. package/dist/.next/static/chunks/app/layout-8360fab69b848283.js +0 -1
  212. package/dist/.next/static/chunks/app/page-5990cb23b8bd27c4.js +0 -1
  213. package/dist/.next/static/css/674bee6608c638e4.css +0 -3
  214. /package/dist/.next/.next/static/{7XBqETNmhSxMXhw8iUpEa → OUHnbB-64afrrZQL0Srn5}/_buildManifest.js +0 -0
  215. /package/dist/.next/.next/static/{7XBqETNmhSxMXhw8iUpEa → OUHnbB-64afrrZQL0Srn5}/_ssgManifest.js +0 -0
  216. /package/dist/.next/{static/MoZBULXFiTFzrY1JVK_Df → .next/static/ZBTCAyp1kO5FdEHMU-Dw2}/_buildManifest.js +0 -0
  217. /package/dist/.next/{static/MoZBULXFiTFzrY1JVK_Df → .next/static/ZBTCAyp1kO5FdEHMU-Dw2}/_ssgManifest.js +0 -0
@@ -112,26 +112,18 @@ export function parseStatement(sql) {
112
112
  // Extract the index name.
113
113
  const indexMatch = cleanSql.match(/INDEX\s+(?:IF\s+NOT\s+EXISTS\s+)?([\w$]+)/i);
114
114
  const indexName = indexMatch ? indexMatch[1] : '';
115
- // Extract the columns from the ON clause.
116
- const onMatch = cleanSql.match(/ON\s+[\w$]+\s*\(([^)]+)\)/i);
117
- const columns = onMatch ? onMatch[1].trim() : '';
118
- // Remove spaces and replace commas with hyphens.
119
- const formattedColumns = columns.replace(/\s+/g, '').replace(/,/g, '-').toLowerCase();
120
115
 
121
- // Build a description that uses the index name,
122
- // and appends the columns if they are not already included.
123
- let description = indexName;
124
- if (formattedColumns && !description.toLowerCase().includes(formattedColumns)) {
125
- description += `-${formattedColumns}`;
126
- }
116
+ // For index migrations, simply use the index name as the description.
117
+ const description = indexName;
127
118
 
128
119
  return {
129
120
  type: Operations.CREATE_INDEX,
130
121
  tableName,
131
122
  sql: cleanSql,
132
- description // e.g. "idx_movies_name" or "idx_movies_name-name"
123
+ description // now simply the index name, e.g. "idx_technology_timestamp"
133
124
  };
134
- }
125
+ }
126
+
135
127
 
136
128
  if (upperSql.startsWith('DROP INDEX')) {
137
129
  const tableName = extractTableNameFromIndex(cleanSql);
@@ -559,14 +551,14 @@ export function formatMigrationContent(sql) {
559
551
  * @returns {Promise<string>} The migration file content.
560
552
  */
561
553
 
562
- export async function getMigrationFileContent(projectDir, databaseName, migrationFile, env) {
554
+ export async function getMigrationFileContent(projectDir, databaseName, migrationFile, env) {
563
555
  try {
564
556
  // Extract table name using improved helper
565
557
  const tableName = await extractTableNameFromMigration(migrationFile);
566
558
  let sql;
567
559
 
568
- if (tableName) {
569
- // Try getting SQL from sqlite_master for table
560
+ // For non-index migrations, try getting SQL from sqlite_master for table
561
+ if (tableName && !migrationFile.includes('create-index')) {
570
562
  const result = await executeSQL(
571
563
  projectDir,
572
564
  databaseName,
@@ -583,24 +575,38 @@ export function formatMigrationContent(sql) {
583
575
  }
584
576
  }
585
577
 
586
- // If we couldn't get the SQL from table, try index
578
+ // If we couldn't get the SQL from table and it's an index migration, try fetching via index name.
587
579
  if (!sql && migrationFile.includes('create-index')) {
588
- const indexMatch = migrationFile.match(/create-index[-_]([^_.]+)/);
580
+ const indexMatch = migrationFile.match(/create-index[_-](.+)\.sql$/i);
589
581
  if (indexMatch) {
590
- const result = await executeSQL(
582
+ let indexName = indexMatch[1]; // e.g. "idx_technology_timestamp-technology-timestamp"
583
+ let result = await executeSQL(
591
584
  projectDir,
592
585
  databaseName,
593
- `SELECT sql FROM sqlite_master WHERE type='index' AND name='${indexMatch[1]}';`,
586
+ `SELECT sql FROM sqlite_master WHERE type='index' AND name='${indexName}' COLLATE NOCASE;`,
594
587
  env,
595
588
  true
596
589
  );
597
- const parsed = JSON.parse(result);
590
+ let parsed = JSON.parse(result);
598
591
  sql = parsed[0]?.results?.[0]?.sql;
592
+ // Fallback: if no result, try using only the part before the first hyphen.
593
+ if (!sql) {
594
+ const fallbackIndexName = indexName.split('-')[0]; // "idx_technology_timestamp"
595
+ result = await executeSQL(
596
+ projectDir,
597
+ databaseName,
598
+ `SELECT sql FROM sqlite_master WHERE type='index' AND name='${fallbackIndexName}' COLLATE NOCASE;`,
599
+ env,
600
+ true
601
+ );
602
+ parsed = JSON.parse(result);
603
+ sql = parsed[0]?.results?.[0]?.sql;
604
+ }
599
605
  }
600
- }
606
+ }
601
607
 
602
- // If still no SQL, try fetching any object matching the name pattern
603
- if (!sql && tableName) {
608
+ // If still no SQL and it's not an index migration, try a fallback query
609
+ if (!sql && tableName && !migrationFile.includes('create-index')) {
604
610
  const result = await executeSQL(
605
611
  projectDir,
606
612
  databaseName,
@@ -0,0 +1,34 @@
1
+ // lib/version-utils.js
2
+
3
+ /**
4
+ * Compares semantic versions to determine if v2 is newer than v1
5
+ * @param {string} v1 - First version (e.g. "1.0.0")
6
+ * @param {string} v2 - Second version (e.g. "1.0.1")
7
+ * @returns {boolean} True if v2 is newer than v1
8
+ */
9
+ export function isNewerVersion(v1, v2) {
10
+ if (!v1 || !v2) return false;
11
+
12
+ // Split versions by dots
13
+ const v1Parts = v1.split('.').map(Number);
14
+ const v2Parts = v2.split('.').map(Number);
15
+
16
+ // Compare major, minor, and patch versions
17
+ for (let i = 0; i < 3; i++) {
18
+ if (v2Parts[i] > v1Parts[i]) return true;
19
+ if (v2Parts[i] < v1Parts[i]) return false;
20
+ }
21
+
22
+ // If we reach here, versions are equal
23
+ return false;
24
+ }
25
+
26
+ /**
27
+ * Formats version strings for display by removing leading 'v' if present
28
+ * @param {string} version - The version string (e.g. "v1.0.0" or "1.0.0")
29
+ * @returns {string} Formatted version (e.g. "1.0.0")
30
+ */
31
+ export function formatVersion(version) {
32
+ if (!version) return '';
33
+ return version.startsWith('v') ? version.substring(1) : version;
34
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "d1s",
3
- "version": "0.2.9",
3
+ "version": "0.2.11",
4
4
  "type": "module",
5
5
  "description": "D1 Studio – a native studio for managing Cloudflare D1 databases.",
6
6
  "main": "./dist/d1-studio.js",
@@ -97,4 +97,4 @@
97
97
  "tailwindcss": "^3.4.1",
98
98
  "wrangler": "^3.107.3"
99
99
  }
100
- }
100
+ }
package/scripts/studio.js CHANGED
@@ -3,10 +3,6 @@ import chalk from 'chalk';
3
3
  import open from 'open';
4
4
  import { fileURLToPath } from 'url';
5
5
  import { dirname, join } from 'path';
6
- import { createHash } from 'crypto';
7
- import { homedir } from 'os';
8
- import os from 'os';
9
- import { readFile, writeFile, mkdir } from 'node:fs/promises';
10
6
  import express from 'express';
11
7
  import { createServer } from 'http';
12
8
  import next from 'next';
@@ -22,139 +18,42 @@ if (hasDebugFlag) {
22
18
  logger.debug('Debug mode enabled');
23
19
  }
24
20
 
25
- const notifier = updateNotifier({ pkg, updateCheckInterval: 1000 * 60 * 60 * 24 });
26
- notifier.notify({ defer: false });
27
-
28
- // Compute the absolute path of this file and its directory
29
- const __filename = fileURLToPath(import.meta.url);
30
- const __dirname = dirname(__filename);
31
-
32
- // License validation utilities
33
- class LicenseError extends Error {
34
- constructor(message, type = 'VALIDATION_ERROR') {
35
- super(message);
36
- this.name = 'LicenseError';
37
- this.type = type;
38
- }
39
- }
40
-
41
- class StudioLicenseManager {
42
- constructor() {
43
- this.licenseDir = join(homedir(), '.d1studio');
44
- this.licensePath = join(this.licenseDir, 'license.json');
45
- }
46
-
47
- async checkLicenseFile() {
48
- try {
49
- const licenseData = JSON.parse(await readFile(this.licensePath, 'utf8'));
50
-
51
- // If no end_date or subscription_status, the license is valid (perpetual license)
52
- if (!licenseData.end_date || !licenseData.subscription_status) {
53
- return {
54
- licenseData,
55
- isValid: true,
56
- isExpiringSoon: false
57
- };
58
- }
59
-
60
- // Check subscription status first
61
- if (licenseData.subscription_status !== 'active') {
62
- return {
63
- licenseData,
64
- isValid: false,
65
- isExpiringSoon: false
66
- };
67
- }
68
-
69
- const endDate = new Date(licenseData.end_date);
70
- const now = new Date();
71
-
72
- // Check if subscription has ended
73
- if (endDate < now) {
74
- return {
75
- licenseData,
76
- isValid: false,
77
- isExpiringSoon: false
78
- };
79
- }
21
+ const notifier = updateNotifier({
22
+ pkg,
23
+ updateCheckInterval: 1000 * 60 * 60 * 24,
24
+ });
80
25
 
81
- // Check if subscription ends in next 7 days
82
- const sevenDaysFromNow = new Date(now.getTime() + (7 * 24 * 60 * 60 * 1000));
83
- const isExpiringSoon = endDate <= sevenDaysFromNow;
26
+ // Check if update is available
27
+ if (notifier.update) {
28
+ // Get current version and latest version
29
+ const currentVersion = pkg.version;
30
+ const latestVersion = notifier.update.latest;
31
+
32
+ // Version-specific messages
33
+ const versionMessages = {
34
+ // Add specific version messages here
35
+ '0.2.11': 'Fixed table toolbar and table view horizontal/vertical scroll issues'
36
+ // Add more version messages as needed
37
+ };
84
38
 
85
- return {
86
- licenseData,
87
- isValid: true,
88
- isExpiringSoon
89
- };
90
- } catch (error) {
91
- return null;
92
- }
39
+ // Create custom message
40
+ let message = `Update available ${chalk.dim(currentVersion)} → ${chalk.green(latestVersion)}`;
41
+
42
+ // Add version-specific message if available
43
+ if (versionMessages[latestVersion]) {
44
+ message += `\n${chalk.cyan('What\'s new:')} ${versionMessages[latestVersion]}`;
93
45
  }
46
+
47
+ // Add installation instructions
48
+ message += `\nRun ${chalk.cyan('npm i -g d1s@latest')} to update`;
94
49
 
95
- async saveLicenseFile(licenseData) {
96
- try {
97
- await mkdir(this.licenseDir, { recursive: true });
98
- await writeFile(this.licensePath, JSON.stringify(licenseData, null, 2));
99
- } catch (error) {
100
- throw new LicenseError('Failed to save license information', 'SAVE_ERROR');
101
- }
102
- }
103
- }
104
-
105
- async function generateHardwareId() {
106
- const osInfo = {
107
- platform: process.platform,
108
- arch: process.arch,
109
- cpus: os.cpus().length,
110
- hostname: os.hostname(),
111
- username: os.userInfo().username,
112
- };
113
- const systemString = JSON.stringify(osInfo);
114
- return createHash('sha256').update(systemString).digest('hex');
50
+ // Display custom notification
51
+ notifier.notify({ message });
115
52
  }
116
53
 
117
- async function validateLicenseRemote(licenseKey, email, hardwareId) {
118
- const baseUrl =
119
- process.env.NODE_ENV === 'development'
120
- ? 'http://localhost:4000'
121
- : 'https://d1-studio.com';
122
-
123
- try {
124
- const response = await fetch(`${baseUrl}/api/validate-license`, {
125
- method: 'POST',
126
- headers: { 'Content-Type': 'application/json' },
127
- body: JSON.stringify({ licenseKey, email, hardwareId }),
128
- });
129
-
130
- const result = await response.json();
131
-
132
- if (!response.ok) {
133
- throw new LicenseError(
134
- result.message || 'License validation failed',
135
- result.type || 'VALIDATION_ERROR'
136
- );
137
- }
138
-
139
- return result;
140
- } catch (error) {
141
- if (error instanceof LicenseError) {
142
- throw error;
143
- }
144
- if (error.name === 'TypeError' && error.message.includes('fetch')) {
145
- throw new LicenseError(
146
- process.env.NODE_ENV === 'development'
147
- ? 'Unable to connect to local license validation service. Make sure the development server is running.'
148
- : 'Unable to connect to license server. Please check your internet connection.',
149
- 'CONNECTION_ERROR'
150
- );
151
- }
152
- throw new LicenseError(
153
- 'An unexpected error occurred during license validation',
154
- 'VALIDATION_ERROR'
155
- );
156
- }
157
- }
54
+ // Compute the absolute path of this file and its directory
55
+ const __filename = fileURLToPath(import.meta.url);
56
+ const __dirname = dirname(__filename);
158
57
 
159
58
  async function startServer(port, projectDirectory) {
160
59
  return new Promise((resolvePromise, rejectPromise) => {
@@ -210,69 +109,6 @@ async function startStudio() {
210
109
  try {
211
110
  logger.info(chalk.green('Initializing D1 Studio...'));
212
111
 
213
- const licenseManager = new StudioLicenseManager();
214
-
215
- // Check existing license
216
- const licenseCheck = await licenseManager.checkLicenseFile();
217
-
218
- // Handle license validation
219
- if (!licenseCheck || !licenseCheck.isValid) {
220
- // Prompt for license information if needed
221
- const { default: inquirer } = await import('inquirer');
222
- logger.info(chalk.yellow('\nPlease enter your D1 Studio license information:'));
223
-
224
- const answers = await inquirer.prompt([
225
- {
226
- type: 'input',
227
- name: 'licenseKey',
228
- message: 'License Key:',
229
- validate: (input) => input.length > 0 || 'License key is required',
230
- },
231
- {
232
- type: 'input',
233
- name: 'email',
234
- message: 'Email:',
235
- validate: (input) => {
236
- const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
237
- return emailRegex.test(input) || 'Please enter a valid email';
238
- },
239
- },
240
- ]);
241
-
242
- const hardwareId = await generateHardwareId();
243
-
244
- try {
245
- const validation = await validateLicenseRemote(
246
- answers.licenseKey,
247
- answers.email,
248
- hardwareId
249
- );
250
-
251
- // Save the complete validation response
252
- await licenseManager.saveLicenseFile({
253
- licenseKey: answers.licenseKey,
254
- email: answers.email,
255
- hardwareId,
256
- end_date: validation.end_date,
257
- subscription_status: validation.subscription_status,
258
- type: validation.licenseType,
259
- activatedAt: new Date().toISOString()
260
- });
261
-
262
- logger.info(chalk.green('\nLicense activated successfully!'));
263
- } catch (error) {
264
- logger.error(chalk.red(`\nLicense validation failed: ${error.message}`));
265
- process.exit(1);
266
- }
267
- } else if (licenseCheck.isExpiringSoon) {
268
- // Show warning about expiring subscription
269
- const endDate = new Date(licenseCheck.licenseData.end_date).toLocaleDateString();
270
- logger.warn(chalk.yellow(`
271
- ⚠️ Your subscription will expire on ${endDate}
272
- Please renew your subscription at https://d1-studio.com to continue using D1 Studio.
273
- `));
274
- }
275
-
276
112
  // Get wrangler configuration
277
113
  try {
278
114
  const userProjectPath = process.cwd();
@@ -1,31 +1,40 @@
1
1
  // app/api/update/route.js
2
2
  import { NextResponse } from 'next/server';
3
- import updateNotifier from 'update-notifier';
4
3
  import pkg from '../../../../package.json' assert { type: 'json' };
5
4
 
6
5
  export async function GET() {
7
6
  try {
8
- // Create notifier instance with package info
9
- const notifier = updateNotifier({
10
- pkg,
11
- updateCheckInterval: 1000 * 60 * 60 * 24 // Check once per day
7
+ // Get current version from package.json
8
+ const currentVersion = pkg.version;
9
+
10
+ // Fetch the latest version from npm registry
11
+ const response = await fetch('https://registry.npmjs.org/d1s/latest', {
12
+ headers: {
13
+ 'Accept': 'application/json',
14
+ 'User-Agent': 'd1-studio'
15
+ },
16
+ // Add cache control to prevent excessive requests
17
+ cache: 'no-cache'
12
18
  });
13
-
14
- // If there's an update, notifier.update will contain the update info
15
- if (notifier.update) {
16
- const { latest, current } = notifier.update;
17
-
18
- // Only return update info if versions are different
19
- if (latest !== current) {
20
- return NextResponse.json({
21
- update: {
22
- latest,
23
- current
24
- }
25
- });
26
- }
19
+
20
+ if (!response.ok) {
21
+ throw new Error('Failed to check for updates');
27
22
  }
28
-
23
+
24
+ const data = await response.json();
25
+ const latestVersion = data.version;
26
+
27
+ // Compare versions - only return update info if there's a newer version
28
+ if (latestVersion && currentVersion && latestVersion !== currentVersion) {
29
+ console.log(`Update available: ${currentVersion} → ${latestVersion}`);
30
+ return NextResponse.json({
31
+ update: {
32
+ latest: latestVersion,
33
+ current: currentVersion
34
+ }
35
+ });
36
+ }
37
+
29
38
  // No update available
30
39
  return NextResponse.json({ update: null });
31
40
  } catch (error) {
package/src/app/page.jsx CHANGED
@@ -5,7 +5,6 @@ import { useState } from 'react';
5
5
  import { AppSidebar } from '../components/sidebar';
6
6
  import { SQLEditor } from '../components/sql-editor';
7
7
  import { TableView } from '../components/table-view';
8
- import UpdateBanner from '@/components/update-banner';
9
8
 
10
9
  export default function StudioPage() {
11
10
  const [currentView, setCurrentView] = useState('sql-editor');
@@ -17,28 +16,19 @@ export default function StudioPage() {
17
16
  };
18
17
 
19
18
  return (
20
- <div className="flex flex-col min-h-screen bg-background">
21
- {/* Banner */}
22
- <UpdateBanner />
23
-
24
- {/* Main layout */}
25
- <div className="flex flex-1">
26
- {/* Sidebar */}
27
- <AppSidebar
28
- className="w-64 border-r fixed h-[calc(100vh-var(--banner-height,0px))]"
29
- currentView={currentView}
30
- onNavigate={setCurrentView}
31
- onTableSelect={handleTableSelect}
32
- />
33
-
34
- {/* Main content */}
35
- <main className="ml-64 flex-1 h-[calc(100vh-var(--banner-height,0px))]">
36
- {currentView === 'sql-editor' && <SQLEditor />}
37
- {currentView === 'table-view' && selectedTable && (
38
- <TableView tableName={selectedTable} />
39
- )}
40
- </main>
41
- </div>
19
+ <div className="flex h-screen bg-background">
20
+ <AppSidebar
21
+ className="w-64 border-r flex-shrink-0"
22
+ currentView={currentView}
23
+ onNavigate={setCurrentView}
24
+ onTableSelect={handleTableSelect}
25
+ />
26
+ <main className="flex-1 overflow-hidden">
27
+ {currentView === 'sql-editor' && <SQLEditor />}
28
+ {currentView === 'table-view' && selectedTable && (
29
+ <TableView tableName={selectedTable} />
30
+ )}
31
+ </main>
42
32
  </div>
43
33
  );
44
34
  }
@@ -133,7 +133,8 @@ export function AppSidebar({
133
133
 
134
134
  return (
135
135
  <>
136
- <aside className={`${className} w-64 fixed top-0 left-0 h-full bg-neutral-50 p-4`}>
136
+ {/* The sidebar now has top position adjusted for the banner */}
137
+ <aside className={`${className} top-[var(--banner-height,0px)] left-0 h-[calc(100vh-var(--banner-height,0px))] bg-neutral-50 p-4`}>
137
138
  <div className="flex flex-col h-full font-sans">
138
139
  {/* Header */}
139
140
  <div className="flex items-center gap-2 mb-4">
@@ -684,7 +684,7 @@ export function TableView({ tableName }) {
684
684
  />
685
685
 
686
686
  <ResizableTable columns={schema} onColumnsResize={handleColumnResize}>
687
- <div className="relative flex-1 overflow-hidden bg-background font-mono">
687
+ <div className="flex-1 overflow-hidden bg-background font-mono">
688
688
  <ScrollArea className="w-full h-screen">
689
689
  <div className="relative w-max min-w-fit pb-4">
690
690
  <Table>
@@ -2,61 +2,92 @@
2
2
  'use client'
3
3
 
4
4
  import { useEffect, useState } from 'react';
5
- import { Download } from 'lucide-react';
5
+ import { Info } from 'lucide-react';
6
6
  import axios from 'axios';
7
7
 
8
8
  export default function UpdateBanner() {
9
9
  const [updateInfo, setUpdateInfo] = useState(null);
10
- const [visible, setVisible] = useState(true);
10
+ const [visible, setVisible] = useState(false);
11
+ const [loading, setLoading] = useState(true);
11
12
 
12
13
  useEffect(() => {
13
- const dismissedVersion = localStorage.getItem('updateBannerDismissedVersion');
14
- if (dismissedVersion) {
15
- setVisible(false);
16
- }
17
- }, []);
18
-
19
- useEffect(() => {
20
- const timer = setTimeout(() => {
21
- axios
22
- .get('/api/update')
23
- .then(({ data }) => {
24
- if (data?.update) {
25
- const dismissedVersion = localStorage.getItem('updateBannerDismissedVersion');
26
- if (dismissedVersion !== data.update.latest) {
27
- setUpdateInfo(data.update);
28
- setVisible(true);
29
- }
14
+ const checkForUpdates = async () => {
15
+ try {
16
+ setLoading(true);
17
+
18
+ // Check if we've already dismissed this specific version update
19
+ const dismissedVersion = localStorage.getItem('dismissedVersion');
20
+
21
+ const { data } = await axios.get('/api/update');
22
+
23
+ if (data?.update) {
24
+ // Only show if the user hasn't dismissed this specific version
25
+ if (dismissedVersion !== data.update.latest) {
26
+ setUpdateInfo(data.update);
27
+ setVisible(true);
28
+ } else {
29
+ setVisible(false);
30
30
  }
31
- })
32
- .catch(console.error);
33
- }, 1000);
31
+ } else {
32
+ setVisible(false);
33
+ }
34
+ } catch (error) {
35
+ console.error('Error checking for updates:', error);
36
+ setVisible(false);
37
+ } finally {
38
+ setLoading(false);
39
+ }
40
+ };
34
41
 
35
- return () => clearTimeout(timer);
42
+ // Check immediately and then daily
43
+ checkForUpdates();
44
+ const interval = setInterval(checkForUpdates, 24 * 60 * 60 * 1000); // Check daily
45
+
46
+ return () => clearInterval(interval);
36
47
  }, []);
37
48
 
38
- if (!updateInfo || !visible) return null;
49
+ // If banner is dismissed or no update info, don't render and reset CSS variable
50
+ if (!updateInfo || !visible || loading) {
51
+ // Reset the banner height when hidden
52
+ if (typeof document !== 'undefined') {
53
+ document.documentElement.style.setProperty('--banner-height', '0px');
54
+ }
55
+ return null;
56
+ }
57
+
58
+ // Set the banner height CSS variable when visible
59
+ if (typeof document !== 'undefined') {
60
+ document.documentElement.style.setProperty('--banner-height', '37px'); // Adjust based on actual banner height
61
+ }
39
62
 
40
63
  const handleClose = () => {
41
- localStorage.setItem('updateBannerDismissedVersion', updateInfo.latest);
64
+ // Store the dismissed version to avoid showing the same update again
65
+ if (updateInfo?.latest) {
66
+ localStorage.setItem('dismissedVersion', updateInfo.latest);
67
+ }
42
68
  setVisible(false);
43
69
  };
44
70
 
45
71
  return (
46
- <div className="w-full bg-neutral-100 border-b px-3 py-2 flex items-center justify-between">
47
- <div className="flex items-center gap-2 text-sm">
48
- <Download className="h-4 w-4 text-blue-500" />
49
- <span>
50
- Version {updateInfo.latest} is available — npm install -g d1s@latest
51
- </span>
72
+ <div className="fixed top-0 left-0 right-0 z-50 bg-yellow-200 border-b border-yellow-500 px-3 py-2 font-sans">
73
+ <div className="flex items-center justify-center relative">
74
+ {/* Info icon and text centered */}
75
+ <div className="flex items-center gap-2 mx-auto">
76
+ <Info className="h-4 w-4 text-neutral-900" />
77
+ <span className="text-sm font-medium">
78
+ Version {updateInfo.latest} is available — npm install -g d1s@latest
79
+ </span>
80
+ </div>
81
+
82
+ {/* Close button positioned absolutely on the right */}
83
+ <button
84
+ onClick={handleClose}
85
+ className="absolute right-0 text-neutral-500 hover:text-neutral-700"
86
+ aria-label="Close update banner"
87
+ >
88
+ ×
89
+ </button>
52
90
  </div>
53
- <button
54
- onClick={handleClose}
55
- className="text-neutral-500 hover:text-neutral-700"
56
- aria-label="Close update banner"
57
- >
58
- ×
59
- </button>
60
91
  </div>
61
92
  );
62
93
  }