@t1mmen/srtd 0.3.0 → 0.4.1

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 (186) hide show
  1. package/README.md +85 -118
  2. package/dist/package.json +97 -0
  3. package/dist/src/__tests__/apply.test.js +69 -0
  4. package/dist/src/__tests__/apply.test.js.map +1 -0
  5. package/dist/src/__tests__/build.test.js +67 -0
  6. package/dist/src/__tests__/build.test.js.map +1 -0
  7. package/dist/{__tests__ → src/__tests__}/vitest.setup.js +41 -25
  8. package/dist/src/__tests__/vitest.setup.js.map +1 -0
  9. package/dist/{__tests__ → src/__tests__}/watch.test.js +9 -3
  10. package/dist/src/__tests__/watch.test.js.map +1 -0
  11. package/dist/src/cli.js.map +1 -0
  12. package/dist/src/commands/_app.js +29 -0
  13. package/dist/src/commands/_app.js.map +1 -0
  14. package/dist/{commands → src/commands}/apply.d.ts +2 -1
  15. package/dist/src/commands/apply.js +30 -0
  16. package/dist/src/commands/apply.js.map +1 -0
  17. package/dist/{commands → src/commands}/build.d.ts +5 -1
  18. package/dist/src/commands/build.js +41 -0
  19. package/dist/src/commands/build.js.map +1 -0
  20. package/dist/src/commands/clear.d.ts +2 -0
  21. package/dist/src/commands/clear.js +45 -0
  22. package/dist/src/commands/clear.js.map +1 -0
  23. package/dist/{commands → src/commands}/index.js +11 -5
  24. package/dist/src/commands/index.js.map +1 -0
  25. package/dist/src/commands/init.js.map +1 -0
  26. package/dist/{commands → src/commands}/register.js +6 -5
  27. package/dist/src/commands/register.js.map +1 -0
  28. package/dist/{commands → src/commands}/watch.js +61 -33
  29. package/dist/src/commands/watch.js.map +1 -0
  30. package/dist/{components → src/components}/Branding.js +7 -7
  31. package/dist/src/components/Branding.js.map +1 -0
  32. package/dist/src/components/Debug.d.ts +2 -0
  33. package/dist/src/components/Debug.js +160 -0
  34. package/dist/src/components/Debug.js.map +1 -0
  35. package/dist/src/components/ProcessingResults.d.ts +9 -0
  36. package/dist/src/components/ProcessingResults.js +32 -0
  37. package/dist/src/components/ProcessingResults.js.map +1 -0
  38. package/dist/{components → src/components}/Quittable.js +1 -1
  39. package/dist/src/components/Quittable.js.map +1 -0
  40. package/dist/src/components/TimeSince.js.map +1 -0
  41. package/dist/src/components/customTheme.d.ts +10 -0
  42. package/dist/src/components/customTheme.js +66 -0
  43. package/dist/src/components/customTheme.js.map +1 -0
  44. package/dist/src/constants.js.map +1 -0
  45. package/dist/src/hooks/useDatabaseConnection.js +57 -0
  46. package/dist/src/hooks/useDatabaseConnection.js.map +1 -0
  47. package/dist/{hooks → src/hooks}/useTemplateManager.d.ts +6 -8
  48. package/dist/src/hooks/useTemplateManager.js +124 -0
  49. package/dist/src/hooks/useTemplateManager.js.map +1 -0
  50. package/dist/src/hooks/useTemplateProcessor.d.ts +11 -0
  51. package/dist/src/hooks/useTemplateProcessor.js +71 -0
  52. package/dist/src/hooks/useTemplateProcessor.js.map +1 -0
  53. package/dist/src/hooks/useTemplateState.js.map +1 -0
  54. package/dist/{lib → src/lib}/templateManager.d.ts +13 -2
  55. package/dist/{lib → src/lib}/templateManager.js +172 -50
  56. package/dist/src/lib/templateManager.js.map +1 -0
  57. package/dist/src/lib/templateManager.test.js +729 -0
  58. package/dist/src/lib/templateManager.test.js.map +1 -0
  59. package/dist/{types.d.ts → src/types.d.ts} +3 -0
  60. package/dist/src/types.js.map +1 -0
  61. package/dist/src/utils/applyMigration.js.map +1 -0
  62. package/dist/src/utils/applyMigrations.test.js.map +1 -0
  63. package/dist/src/utils/calculateMD5.js.map +1 -0
  64. package/dist/{utils → src/utils}/config.d.ts +2 -0
  65. package/dist/src/utils/config.js +65 -0
  66. package/dist/src/utils/config.js.map +1 -0
  67. package/dist/src/utils/config.test.js.map +1 -0
  68. package/dist/src/utils/createEmptyBuildLog.js.map +1 -0
  69. package/dist/{utils → src/utils}/databaseConnection.d.ts +5 -0
  70. package/dist/{utils → src/utils}/databaseConnection.js +40 -13
  71. package/dist/src/utils/databaseConnection.js.map +1 -0
  72. package/dist/src/utils/databaseConnection.test.d.ts +1 -0
  73. package/dist/src/utils/databaseConnection.test.js.map +1 -0
  74. package/dist/src/utils/ensureDirectories.js.map +1 -0
  75. package/dist/src/utils/fileExists.js.map +1 -0
  76. package/dist/src/utils/getNextTimestamp.js.map +1 -0
  77. package/dist/src/utils/isWipTemplate.js.map +1 -0
  78. package/dist/src/utils/loadBuildLog.js.map +1 -0
  79. package/dist/src/utils/loadBuildLog.test.d.ts +1 -0
  80. package/dist/src/utils/loadBuildLog.test.js.map +1 -0
  81. package/dist/src/utils/logger.js.map +1 -0
  82. package/dist/src/utils/registerTemplate.js.map +1 -0
  83. package/dist/src/utils/safeCreate.js.map +1 -0
  84. package/dist/src/utils/saveBuildLog.js.map +1 -0
  85. package/dist/src/utils/store.d.ts +5 -0
  86. package/dist/src/utils/store.js +10 -0
  87. package/dist/src/utils/store.js.map +1 -0
  88. package/package.json +4 -3
  89. package/dist/__tests__/vitest.setup.js.map +0 -1
  90. package/dist/__tests__/watch.test.js.map +0 -1
  91. package/dist/cli.js.map +0 -1
  92. package/dist/commands/_app.js +0 -27
  93. package/dist/commands/_app.js.map +0 -1
  94. package/dist/commands/apply.js +0 -32
  95. package/dist/commands/apply.js.map +0 -1
  96. package/dist/commands/build.js +0 -25
  97. package/dist/commands/build.js.map +0 -1
  98. package/dist/commands/index.js.map +0 -1
  99. package/dist/commands/init.js.map +0 -1
  100. package/dist/commands/register.js.map +0 -1
  101. package/dist/commands/watch.js.map +0 -1
  102. package/dist/components/Branding.js.map +0 -1
  103. package/dist/components/Quittable.js.map +0 -1
  104. package/dist/components/TimeSince.js.map +0 -1
  105. package/dist/constants.js.map +0 -1
  106. package/dist/hooks/useDatabaseConnection.js +0 -68
  107. package/dist/hooks/useDatabaseConnection.js.map +0 -1
  108. package/dist/hooks/useTemplateManager.js +0 -141
  109. package/dist/hooks/useTemplateManager.js.map +0 -1
  110. package/dist/hooks/useTemplateState.js.map +0 -1
  111. package/dist/lib/templateManager.js.map +0 -1
  112. package/dist/lib/templateManager.test.js +0 -289
  113. package/dist/lib/templateManager.test.js.map +0 -1
  114. package/dist/types.js.map +0 -1
  115. package/dist/utils/applyMigration.js.map +0 -1
  116. package/dist/utils/applyMigrations.test.js.map +0 -1
  117. package/dist/utils/calculateMD5.js.map +0 -1
  118. package/dist/utils/config.js +0 -79
  119. package/dist/utils/config.js.map +0 -1
  120. package/dist/utils/config.test.js.map +0 -1
  121. package/dist/utils/createEmptyBuildLog.js.map +0 -1
  122. package/dist/utils/databaseConnection.js.map +0 -1
  123. package/dist/utils/databaseConnection.test.js.map +0 -1
  124. package/dist/utils/ensureDirectories.js.map +0 -1
  125. package/dist/utils/fileExists.js.map +0 -1
  126. package/dist/utils/getNextTimestamp.js.map +0 -1
  127. package/dist/utils/isWipTemplate.js.map +0 -1
  128. package/dist/utils/loadBuildLog.js.map +0 -1
  129. package/dist/utils/loadBuildLog.test.js.map +0 -1
  130. package/dist/utils/logger.js.map +0 -1
  131. package/dist/utils/registerTemplate.js.map +0 -1
  132. package/dist/utils/safeCreate.js.map +0 -1
  133. package/dist/utils/saveBuildLog.js.map +0 -1
  134. /package/dist/{__tests__/watch.test.d.ts → src/__tests__/apply.test.d.ts} +0 -0
  135. /package/dist/{lib/templateManager.test.d.ts → src/__tests__/build.test.d.ts} +0 -0
  136. /package/dist/{__tests__ → src/__tests__}/vitest.setup.d.ts +0 -0
  137. /package/dist/{utils/applyMigrations.test.d.ts → src/__tests__/watch.test.d.ts} +0 -0
  138. /package/dist/{cli.d.ts → src/cli.d.ts} +0 -0
  139. /package/dist/{cli.js → src/cli.js} +0 -0
  140. /package/dist/{commands → src/commands}/_app.d.ts +0 -0
  141. /package/dist/{commands → src/commands}/index.d.ts +0 -0
  142. /package/dist/{commands → src/commands}/init.d.ts +0 -0
  143. /package/dist/{commands → src/commands}/init.js +0 -0
  144. /package/dist/{commands → src/commands}/register.d.ts +0 -0
  145. /package/dist/{commands → src/commands}/watch.d.ts +0 -0
  146. /package/dist/{components → src/components}/Branding.d.ts +0 -0
  147. /package/dist/{components → src/components}/Quittable.d.ts +0 -0
  148. /package/dist/{components → src/components}/TimeSince.d.ts +0 -0
  149. /package/dist/{components → src/components}/TimeSince.js +0 -0
  150. /package/dist/{constants.d.ts → src/constants.d.ts} +0 -0
  151. /package/dist/{constants.js → src/constants.js} +0 -0
  152. /package/dist/{hooks → src/hooks}/useDatabaseConnection.d.ts +0 -0
  153. /package/dist/{hooks → src/hooks}/useTemplateState.d.ts +0 -0
  154. /package/dist/{hooks → src/hooks}/useTemplateState.js +0 -0
  155. /package/dist/{utils/config.test.d.ts → src/lib/templateManager.test.d.ts} +0 -0
  156. /package/dist/{types.js → src/types.js} +0 -0
  157. /package/dist/{utils → src/utils}/applyMigration.d.ts +0 -0
  158. /package/dist/{utils → src/utils}/applyMigration.js +0 -0
  159. /package/dist/{utils/databaseConnection.test.d.ts → src/utils/applyMigrations.test.d.ts} +0 -0
  160. /package/dist/{utils → src/utils}/applyMigrations.test.js +0 -0
  161. /package/dist/{utils → src/utils}/calculateMD5.d.ts +0 -0
  162. /package/dist/{utils → src/utils}/calculateMD5.js +0 -0
  163. /package/dist/{utils/loadBuildLog.test.d.ts → src/utils/config.test.d.ts} +0 -0
  164. /package/dist/{utils → src/utils}/config.test.js +0 -0
  165. /package/dist/{utils → src/utils}/createEmptyBuildLog.d.ts +0 -0
  166. /package/dist/{utils → src/utils}/createEmptyBuildLog.js +0 -0
  167. /package/dist/{utils → src/utils}/databaseConnection.test.js +0 -0
  168. /package/dist/{utils → src/utils}/ensureDirectories.d.ts +0 -0
  169. /package/dist/{utils → src/utils}/ensureDirectories.js +0 -0
  170. /package/dist/{utils → src/utils}/fileExists.d.ts +0 -0
  171. /package/dist/{utils → src/utils}/fileExists.js +0 -0
  172. /package/dist/{utils → src/utils}/getNextTimestamp.d.ts +0 -0
  173. /package/dist/{utils → src/utils}/getNextTimestamp.js +0 -0
  174. /package/dist/{utils → src/utils}/isWipTemplate.d.ts +0 -0
  175. /package/dist/{utils → src/utils}/isWipTemplate.js +0 -0
  176. /package/dist/{utils → src/utils}/loadBuildLog.d.ts +0 -0
  177. /package/dist/{utils → src/utils}/loadBuildLog.js +0 -0
  178. /package/dist/{utils → src/utils}/loadBuildLog.test.js +0 -0
  179. /package/dist/{utils → src/utils}/logger.d.ts +0 -0
  180. /package/dist/{utils → src/utils}/logger.js +0 -0
  181. /package/dist/{utils → src/utils}/registerTemplate.d.ts +0 -0
  182. /package/dist/{utils → src/utils}/registerTemplate.js +0 -0
  183. /package/dist/{utils → src/utils}/safeCreate.d.ts +0 -0
  184. /package/dist/{utils → src/utils}/safeCreate.js +0 -0
  185. /package/dist/{utils → src/utils}/saveBuildLog.d.ts +0 -0
  186. /package/dist/{utils → src/utils}/saveBuildLog.js +0 -0
package/README.md CHANGED
@@ -1,26 +1,32 @@
1
1
  # `srtd` 🪄 Supabase Repeatable Template Definitions
2
2
 
3
- Live-reloading SQL templates for [Supabase](https://supabase.com) projects. DX supercharged! 🚀
3
+
4
+
5
+ > Live-reloading SQL templates for [Supabase](https://supabase.com) projects. DX supercharged! 🚀
4
6
 
5
7
  [![npm version](https://badge.fury.io/js/@t1mmen%2Fsrtd.svg)](https://www.npmjs.com/package/@t1mmen/srtd)
6
8
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
9
  [![CI/CD](https://github.com/t1mmen/srtd/actions/workflows/ci.yml/badge.svg)](https://github.com/t1mmen/srtd/actions/workflows/ci.yml)
8
10
  [![codecov](https://codecov.io/gh/t1mmen/srtd/graph/badge.svg?token=CIMAZ55KCJ)](https://codecov.io/gh/t1mmen/srtd)
9
11
 
12
+
13
+ [![screenshot of srtd](./readme-screenshot.png)](./readme-screenshot.png)
14
+
10
15
  `srtd` enhances the [Supabase](https://supabase.com) DX by adding live-reloading SQL templates into local db. The single-source-of-truth template ➡️ migrations system brings sanity to code reviews, making `git blame` useful.
11
16
 
12
- Built specifically for projects using the standard [Supabase](https://supabase.com) stack (but probably works alright for other Postgres-based projects, too).
13
17
 
14
- **Read the introductory blog post: [Introducing `srtd`: Live-Reloading SQL Templates for Supabase](https://timm.stokke.me/blog/srtd-live-reloading-and-sql-templates-for-supabase)**
18
+ 📖 Blog: [Introducing `srtd`: Live-Reloading SQL Templates for Supabase](https://timm.stokke.me/blog/srtd-live-reloading-and-sql-templates-for-supabase)
15
19
 
16
20
  ## Why This Exists 🤔
17
21
 
18
22
  While building [Timely](https://www.timely.com)'s next-generation [Memory Engine](https://www.timely.com/memory-app) on [Supabase](https://supabase.com), we found ourselves facing two major annoyances:
19
23
 
20
- 1. Code reviews were painful - function changes showed up as complete rewrites rather than helpful diffs
21
- 2. Designing and iterating on database changes locally meant constant friction, like the dance around copy-pasting into SQL console
24
+ 1. Code reviews were painful - function changes showed up as complete rewrites, `git blame` was useless
25
+ 2. Designing and iterating on database changes locally was full of friction, no matter which workflow we tried
26
+
27
+ I spent [nearly two years looking](https://news.ycombinator.com/item?id=37755076) for something pre-existing, to no avail. Sufficiently fed up, I paired with [Claude](https://claude.ai) to eliminate these annoyances.
22
28
 
23
- After over a year of looking-but-not-finding a better way, I paired up with [Claude](https://claude.ai) to eliminate these annoyances. Say hello to `srtd`.
29
+ Say hello to `srtd`.
24
30
 
25
31
  ## Key Features ✨
26
32
 
@@ -29,6 +35,8 @@ After over a year of looking-but-not-finding a better way, I paired up with [Cla
29
35
  - **Just SQL**: Templates build as standard [Supabase](https://supabase.com) migrations when you're ready to deploy
30
36
  - **Developer Friendly**: Interactive CLI with visual feedback for all operations
31
37
 
38
+ Built specifically for projects using the standard [Supabase](https://supabase.com) stack (but probably works alright for other Postgres-based projects, too).
39
+
32
40
  ## Requirements
33
41
 
34
42
  - Node.js v20.x or higher
@@ -83,100 +91,82 @@ srtd build # Creates timestamped migration file
83
91
  supabase migration up # Apply using Supabase CLI
84
92
  ```
85
93
 
86
- ## Commands 🎮
87
-
88
- ### Interactive Mode
89
-
90
- Running `srtd` without arguments opens an interactive menu:
91
94
 
92
- ```
93
- ❯ 🏗️ build - Build Supabase migrations from templates
94
- ▶️ apply - Apply migration templates directly to database
95
- ✍️ register - Register templates as already built
96
- 👀 watch - Watch templates for changes, apply directly to database
97
- ```
98
-
99
- ### CLI Mode
100
-
101
- - 🏗️ `build [--force]` - Generate migrations from templates
102
- - ▶️ `apply [--force]` - Apply templates directly to local database
103
- - ✍️ `register [file.sql]` - Mark templates as already built
104
- - 👀 `watch` - Watch and auto-apply changes
95
+ ## The Power of Templates 💪
105
96
 
106
- > [!IMPORTANT]
107
- > `watch` and `apply` commands modify your local database directly and don't clean up after themselves. Use with caution!
97
+ Without templates, the smallest change to a function would show up as a complete rewrite in your version control system. With templates, the diff is clear and concise.
108
98
 
109
- ## Perfect For 🎯
110
99
 
111
- ### Ideal Use Cases
100
+ ### Perfect For 🎯
112
101
 
113
102
  ✅ Database functions:
114
- ```sql
115
- -- Reusable auth helper
116
- CREATE OR REPLACE FUNCTION auth.user_id()
117
- RETURNS uuid AS $$
118
- SELECT auth.uid()::uuid;
119
- $$ LANGUAGE sql SECURITY DEFINER;
120
-
121
- -- Event notifications
122
- CREATE OR REPLACE FUNCTION notify_changes()
123
- RETURNS trigger AS $$
124
- BEGIN
125
- PERFORM pg_notify(
126
- 'changes',
127
- json_build_object('table', TG_TABLE_NAME, 'id', NEW.id)::text
128
- );
129
- RETURN NEW;
130
- END;
131
- $$ LANGUAGE plpgsql;
103
+ ```diff
104
+ -- Event notifications
105
+ CREATE OR REPLACE FUNCTION notify_changes()
106
+ RETURNS trigger AS $$
107
+ BEGIN
108
+ PERFORM pg_notify(
109
+ 'changes',
110
+ json_build_object('table', TG_TABLE_NAME, 'id', NEW.id)::text
111
+ );
112
+ + RAISE NOTICE 'Notified changes for %', TG_TABLE_NAME; -- Debug logging
113
+ RETURN NEW;
114
+ END;
115
+ $$ LANGUAGE plpgsql;
132
116
  ```
133
117
 
134
118
  ✅ Row-Level Security (RLS):
135
- ```sql
136
- -- Replace/update policies safely
137
- DROP POLICY IF EXISTS "workspace_access" ON resources;
138
- CREATE POLICY "workspace_access" ON resources
139
- USING (workspace_id IN (
140
- SELECT id FROM workspaces
141
- WHERE organization_id = auth.organization_id()
142
- ));
119
+ ```diff
120
+ -- Replace/update policies safely
121
+ DROP POLICY IF EXISTS "workspace_access" ON resources;
122
+ CREATE POLICY "workspace_access" ON resources
123
+ USING (workspace_id IN (
124
+ SELECT id FROM workspaces
125
+ WHERE organization_id = auth.organization_id()
126
+ + AND auth.user_role() NOT IN ('pending')
127
+ ));
143
128
  ```
144
129
 
145
130
  ✅ Views for data abstraction:
146
- ```sql
147
- CREATE OR REPLACE VIEW active_subscriptions AS
148
- SELECT
149
- s.*,
150
- p.name as plan_name,
151
- p.features
152
- FROM subscriptions s
153
- JOIN plans p ON p.id = s.plan_id
154
- WHERE s.status = 'active'
155
- AND s.expires_at > CURRENT_TIMESTAMP;
131
+ ```diff
132
+ CREATE OR REPLACE VIEW active_subscriptions AS
133
+ SELECT
134
+ s.*,
135
+ p.name as plan_name,
136
+ p.features
137
+ FROM subscriptions s
138
+ JOIN plans p ON p.id = s.plan_id
139
+ - WHERE s.status = 'active';
140
+ + WHERE s.status = 'active'
141
+ + AND s.expires_at > CURRENT_TIMESTAMP;
156
142
  ```
157
143
 
158
144
  ✅ Roles and Permissions:
159
- ```sql
160
- -- Revoke all first for clean state
161
- REVOKE ALL ON ALL TABLES IN SCHEMA public FROM public;
145
+ ```diff
146
+ -- Revoke all first for clean state
147
+ REVOKE ALL ON ALL TABLES IN SCHEMA public FROM public;
162
148
 
163
- -- Grant specific access
164
- GRANT USAGE ON SCHEMA public TO authenticated;
165
- GRANT SELECT ON ALL TABLES IN SCHEMA public TO authenticated;
149
+ -- Grant specific access
150
+ GRANT USAGE ON SCHEMA public TO authenticated;
151
+ GRANT SELECT ON ALL TABLES IN SCHEMA public TO authenticated;
152
+ + GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO admin;
166
153
  ```
167
154
 
168
155
  ✅ Safe Type Extensions:
169
- ```sql
170
- DO $$
171
- BEGIN
172
- -- Add new enum values idempotently
173
- IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'notification_type') THEN
174
- CREATE TYPE notification_type AS ENUM ('email', 'sms');
175
- END IF;
176
-
177
- -- Extend existing enum safely
178
- ALTER TYPE notification_type ADD VALUE IF NOT EXISTS 'push';
179
- END $$;
156
+ ```diff
157
+ DO $$
158
+ BEGIN
159
+ -- Add new enum values idempotently
160
+ IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'notification_type') THEN
161
+ CREATE TYPE notification_type AS ENUM ('email', 'sms');
162
+ END IF;
163
+
164
+ -- Extend existing enum safely
165
+ ALTER TYPE notification_type ADD VALUE IF NOT EXISTS 'push';
166
+ ALTER TYPE notification_type ADD VALUE IF NOT EXISTS 'pusher';
167
+ ALTER TYPE notification_type ADD VALUE IF NOT EXISTS 'webhook';
168
+ + ALTER TYPE notification_type ADD VALUE IF NOT EXISTS 'email';
169
+ END $$;
180
170
  ```
181
171
 
182
172
  ### Not Recommended For
@@ -188,46 +178,23 @@ END $$;
188
178
 
189
179
  Use regular [Supabase](https://supabase.com) migrations for these cases.
190
180
 
191
- ## The Power of Templates 💪
192
181
 
193
- Templates make code reviews meaningful. Consider this PR adding priority to a notification function:
182
+ ## Commands 🎮
194
183
 
195
- ```diff
196
- CREATE OR REPLACE FUNCTION dispatch_notification(
197
- user_id uuid,
198
- type text,
199
- payload jsonb
200
- ) RETURNS uuid AS $$
201
- DECLARE
202
- notification_id uuid;
203
- user_settings jsonb;
204
- BEGIN
205
- -- Get user notification settings
206
- SELECT settings INTO user_settings
207
- FROM user_preferences
208
- WHERE id = user_id;
209
-
210
- -- Create notification record
211
- + -- Include priority based on notification type
212
- INSERT INTO notifications (
213
- id,
214
- user_id,
215
- type,
216
- payload,
217
- + priority,
218
- created_at
219
- ) VALUES (
220
- gen_random_uuid(),
221
- dispatch_notification.user_id,
222
- type,
223
- payload,
224
- + COALESCE((SELECT priority FROM notification_types WHERE name = type), 'normal'),
225
- CURRENT_TIMESTAMP
226
- )
227
- RETURNING id INTO notification_id;
228
- ```
184
+ ### Interactive Mode
185
+
186
+ Running `srtd` without arguments opens an interactive menu:
187
+
188
+ ### CLI Mode
229
189
 
230
- Without templates, this would appear as a complete rewrite in your PR.
190
+ - 🏗️ `srtd build [--force]` - Generate migrations from templates
191
+ - ▶️ `srtd apply [--force]` - Apply templates directly to local database
192
+ - ✍️ `srtd register [file.sql]` - Mark templates as already built
193
+ - 👀 `srtd watch` - Watch and auto-apply changes
194
+ - 🧹 `srtd clean` - Remove all logs and reset config
195
+
196
+ > [!IMPORTANT]
197
+ > `watch` and `apply` commands modify your local database directly and don't clean up after themselves. Use with caution!
231
198
 
232
199
  ## Configuration 📝
233
200
 
@@ -0,0 +1,97 @@
1
+ {
2
+ "name": "@t1mmen/srtd",
3
+ "version": "0.4.1",
4
+ "license": "MIT",
5
+ "description": "Supabase Repeatable Template Definitions (srtd): 🪄 Live-reloading SQL templates for Supabase DX. Make your database changes reviewable and migrations maintainable! 🚀",
6
+ "bin": {
7
+ "srtd": "dist/src/cli.js"
8
+ },
9
+ "type": "module",
10
+ "engines": {
11
+ "node": ">=20"
12
+ },
13
+ "scripts": {
14
+ "clean": "rm -rf dist; rm -rf build; rm -rf coverage; npm run supabase:stop",
15
+ "changeset": "changeset",
16
+ "version": "changeset version",
17
+ "release": "npm run build && changeset publish",
18
+ "build": "rm -rf dist && tsc",
19
+ "typecheck": "tsc --noEmit",
20
+ "lint": "biome lint . --write",
21
+ "format": "biome format . --write",
22
+ "dev": "tsc --watch",
23
+ "test": "vitest",
24
+ "test:coverage": "vitest run --coverage --reporter=junit --outputFile=test-report.junit.xml",
25
+ "start": "tsx src/cli.tsx",
26
+ "repomix": "mkdir build; npx repomix",
27
+ "supabase:start": "npx supabase start",
28
+ "supabase:stop": "npx supabase stop"
29
+ },
30
+ "files": [
31
+ "dist"
32
+ ],
33
+ "author": {
34
+ "name": "Timm Stokke",
35
+ "email": "timm@stokke.me",
36
+ "url": "https://timm.stokke.me"
37
+ },
38
+ "publishConfig": {
39
+ "access": "public",
40
+ "registry": "https://registry.npmjs.org/"
41
+ },
42
+ "repository": {
43
+ "type": "git",
44
+ "url": "https://github.com/t1mmen/srtd.git"
45
+ },
46
+ "bugs": {
47
+ "url": "https://github.com/t1mmen/srtd/issues"
48
+ },
49
+ "homepage": "https://github.com/t1mmen/srtd#readme",
50
+ "os": [
51
+ "darwin",
52
+ "linux",
53
+ "win32"
54
+ ],
55
+ "keywords": [
56
+ "cli",
57
+ "migrations",
58
+ "database",
59
+ "postgresql",
60
+ "supabase",
61
+ "sql-templates",
62
+ "live-reload",
63
+ "hot-reload",
64
+ "repeatable-migrations",
65
+ "database-migrations",
66
+ "supabase-cli"
67
+ ],
68
+ "dependencies": {
69
+ "@inkjs/ui": "^2.0.0",
70
+ "chokidar": "^4.0.3",
71
+ "conf": "^13.1.0",
72
+ "glob": "^10.0.0",
73
+ "ink": "^5.1.0",
74
+ "pastel": "^3.0.0",
75
+ "pg": "^8.13.1",
76
+ "react": "^18.3.0",
77
+ "update-notifier": "^7.3.1",
78
+ "zod": "^3.24.1"
79
+ },
80
+ "devDependencies": {
81
+ "@biomejs/biome": "1.9.4",
82
+ "@changesets/cli": "^2.27.11",
83
+ "@sindresorhus/tsconfig": "^7.0.0",
84
+ "@types/glob": "^8.1.0",
85
+ "@types/node": "^20.17.10",
86
+ "@types/pg": "^8.11.10",
87
+ "@types/react": "^18.3.0",
88
+ "@types/update-notifier": "^6.0.8",
89
+ "@vitest/coverage-v8": "^2.1.8",
90
+ "chalk": "^5.4.1",
91
+ "ink-testing-library": "^4.0.0",
92
+ "lefthook": "^1.10.1",
93
+ "tsx": "^4.19.2",
94
+ "typescript": "^5.7.2",
95
+ "vitest": "^2.1.8"
96
+ }
97
+ }
@@ -0,0 +1,69 @@
1
+ import fs from 'node:fs/promises';
2
+ import { tmpdir } from 'node:os';
3
+ import path from 'node:path';
4
+ import { render } from 'ink-testing-library';
5
+ import React from 'react';
6
+ import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
7
+ import Apply from '../commands/apply.js';
8
+ import { connect } from '../utils/databaseConnection.js';
9
+ import { TEST_FN_PREFIX } from './vitest.setup.js';
10
+ vi.mock('../hooks/useTemplateProcessor', () => ({
11
+ useTemplateProcessor: vi.fn().mockImplementation(() => ({
12
+ isProcessing: false,
13
+ result: {
14
+ built: [],
15
+ applied: ['test.sql'],
16
+ skipped: [],
17
+ errors: [],
18
+ },
19
+ })),
20
+ }));
21
+ describe('Apply Command', () => {
22
+ const testContext = {
23
+ timestamp: Date.now(),
24
+ testFunctionName: `${TEST_FN_PREFIX}${Date.now()}`,
25
+ testDir: path.join(tmpdir(), `test-apply-command-${Date.now()}`),
26
+ };
27
+ vi.mock('ink', async (importOriginal) => {
28
+ const actual = (await importOriginal());
29
+ const mockExit = vi.fn();
30
+ return {
31
+ ...actual,
32
+ useApp: () => ({ exit: mockExit }),
33
+ };
34
+ });
35
+ async function createTestTemplate(content) {
36
+ const templateDir = path.join(testContext.testDir, 'test-templates');
37
+ await fs.mkdir(templateDir, { recursive: true });
38
+ const templatePath = path.join(templateDir, `test-${testContext.timestamp}.sql`);
39
+ await fs.writeFile(templatePath, content);
40
+ }
41
+ beforeEach(async () => {
42
+ const validSQL = `
43
+ CREATE OR REPLACE FUNCTION ${testContext.testFunctionName}()
44
+ RETURNS void AS $$
45
+ BEGIN NULL; END;
46
+ $$ LANGUAGE plpgsql;
47
+ `;
48
+ await createTestTemplate(validSQL);
49
+ });
50
+ afterEach(async () => {
51
+ const client = await connect();
52
+ try {
53
+ await client.query(`DROP FUNCTION IF EXISTS ${testContext.testFunctionName}()`);
54
+ }
55
+ finally {
56
+ client.release();
57
+ }
58
+ await fs.rm(testContext.testDir, { recursive: true, force: true });
59
+ });
60
+ test('shows progress and success', async () => {
61
+ const { lastFrame } = render(React.createElement(Apply, { options: { force: false } }));
62
+ expect(lastFrame()).toMatch(/✓ test\.sql/);
63
+ });
64
+ test('handles force flag', async () => {
65
+ const { lastFrame } = render(React.createElement(Apply, { options: { force: true } }));
66
+ expect(lastFrame()).toMatch(/✓ test\.sql/);
67
+ });
68
+ });
69
+ //# sourceMappingURL=apply.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apply.test.js","sourceRoot":"","sources":["../../../src/__tests__/apply.test.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,67 @@
1
+ import fs from 'node:fs/promises';
2
+ import { tmpdir } from 'node:os';
3
+ import path from 'node:path';
4
+ import { render } from 'ink-testing-library';
5
+ import React from 'react';
6
+ import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
7
+ import Build from '../commands/build.js';
8
+ import { TEST_FN_PREFIX } from './vitest.setup.js';
9
+ vi.mock('../hooks/useTemplateProcessor', () => ({
10
+ useTemplateProcessor: vi.fn().mockImplementation(({ apply }) => ({
11
+ isProcessing: false,
12
+ result: {
13
+ built: ['test.sql'],
14
+ applied: apply ? ['test.sql'] : [],
15
+ skipped: [],
16
+ errors: [],
17
+ },
18
+ })),
19
+ }));
20
+ describe('Build Command', () => {
21
+ const testContext = {
22
+ timestamp: Date.now(),
23
+ testFunctionName: `${TEST_FN_PREFIX}${Date.now()}`,
24
+ testDir: path.join(tmpdir(), `test-build-command-${Date.now()}`),
25
+ };
26
+ vi.mock('ink', async (importOriginal) => {
27
+ const actual = (await importOriginal());
28
+ return {
29
+ ...actual,
30
+ useApp: () => ({ exit: vi.fn() }),
31
+ };
32
+ });
33
+ async function createTestTemplate(content) {
34
+ const templateDir = path.join(testContext.testDir, 'test-templates');
35
+ await fs.mkdir(templateDir, { recursive: true });
36
+ const templatePath = path.join(templateDir, `test-${testContext.timestamp}.sql`);
37
+ await fs.writeFile(templatePath, content);
38
+ return templatePath;
39
+ }
40
+ beforeEach(async () => {
41
+ const validSQL = `
42
+ CREATE OR REPLACE FUNCTION ${testContext.testFunctionName}()
43
+ RETURNS void AS $$
44
+ BEGIN NULL; END;
45
+ $$ LANGUAGE plpgsql;
46
+ `;
47
+ await createTestTemplate(validSQL);
48
+ });
49
+ afterEach(async () => {
50
+ await fs.rm(testContext.testDir, { recursive: true, force: true });
51
+ });
52
+ test('shows build progress and success', async () => {
53
+ const { lastFrame } = render(React.createElement(Build, { options: { force: false } }));
54
+ expect(lastFrame()).toMatch(/✓ test\.sql/);
55
+ });
56
+ test('handles force flag', async () => {
57
+ const { lastFrame } = render(React.createElement(Build, { options: { force: true } }));
58
+ expect(lastFrame()).toMatch(/✓ test\.sql/);
59
+ });
60
+ test('handles build and apply together', async () => {
61
+ const { lastFrame } = render(React.createElement(Build, { options: { force: false, apply: true } }));
62
+ // Use more precise matching that accounts for newlines
63
+ expect(lastFrame()).toMatch(/Built:\s*\n\s*✓ test\.sql/);
64
+ expect(lastFrame()).toMatch(/Applied:\s*\n\s*✓ test\.sql/);
65
+ });
66
+ });
67
+ //# sourceMappingURL=build.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build.test.js","sourceRoot":"","sources":["../../../src/__tests__/build.test.tsx"],"names":[],"mappings":"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"}
@@ -5,19 +5,35 @@ import { afterAll, beforeAll, vi } from 'vitest';
5
5
  import { connect, disconnect } from '../utils/databaseConnection.js';
6
6
  export const TEST_FN_PREFIX = 'srtd_scoped_test_func_';
7
7
  export const TEST_ROOT = join(tmpdir(), `srtd-test-${Date.now()}`);
8
- if (process.env.CI) {
9
- let consoleLogMock;
10
- beforeAll(() => {
11
- consoleLogMock = vi.spyOn(console, 'log').mockImplementation(() => {
12
- // Do nothing
13
- });
14
- });
15
- afterAll(() => {
16
- consoleLogMock.mockRestore();
17
- });
18
- }
8
+ vi.mock('../utils/logger', () => ({
9
+ logger: {
10
+ info: () => {
11
+ /** noop */
12
+ },
13
+ success: () => {
14
+ /** noop */
15
+ },
16
+ warn: () => {
17
+ /** noop */
18
+ },
19
+ error: () => {
20
+ /** noop */
21
+ },
22
+ skip: () => {
23
+ /** noop */
24
+ },
25
+ debug: () => {
26
+ /** noop */
27
+ },
28
+ },
29
+ }));
19
30
  beforeAll(async () => {
20
- await fs.mkdir(TEST_ROOT, { recursive: true });
31
+ try {
32
+ await fs.mkdir(TEST_ROOT, { recursive: true });
33
+ }
34
+ catch (error) {
35
+ console.error('Error creating test root:', error, ', retrying once.');
36
+ }
21
37
  });
22
38
  afterAll(async () => {
23
39
  await fs.rm(TEST_ROOT, { recursive: true, force: true });
@@ -26,19 +42,19 @@ afterAll(async () => {
26
42
  try {
27
43
  await client.query('BEGIN');
28
44
  await client.query(`
29
- DO $$
30
- DECLARE
31
- r record;
32
- BEGIN
33
- FOR r IN
34
- SELECT quote_ident(proname) AS func_name
35
- FROM pg_proc
36
- WHERE proname LIKE '${TEST_FN_PREFIX}%'
37
- LOOP
38
- EXECUTE 'DROP FUNCTION IF EXISTS ' || r.func_name;
39
- END LOOP;
40
- END;
41
- $$
45
+ DO $$
46
+ DECLARE
47
+ r record;
48
+ BEGIN
49
+ FOR r IN
50
+ SELECT quote_ident(proname) AS func_name
51
+ FROM pg_proc
52
+ WHERE proname LIKE '${TEST_FN_PREFIX}%'
53
+ LOOP
54
+ EXECUTE 'DROP FUNCTION IF EXISTS ' || r.func_name;
55
+ END LOOP;
56
+ END;
57
+ $$;
42
58
  `);
43
59
  await client.query('COMMIT');
44
60
  }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vitest.setup.js","sourceRoot":"","sources":["../../../src/__tests__/vitest.setup.ts"],"names":[],"mappings":"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"}
@@ -9,15 +9,21 @@ vi.mock('ink', async (importOriginal) => {
9
9
  useApp: () => ({ exit: vi.fn() }),
10
10
  };
11
11
  });
12
+ const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms));
12
13
  describe('Watch Command', () => {
13
14
  beforeEach(() => {
14
15
  vi.clearAllMocks();
15
16
  });
16
17
  test('renders initial state with no templates', async () => {
17
18
  const { lastFrame } = render(React.createElement(Watch, null));
18
- await new Promise(resolve => setTimeout(resolve, 100));
19
- const output = lastFrame();
20
- expect(output).toContain('Watch Mode');
19
+ await wait(100); // Allow time for UI to render and DB to initialize
20
+ expect(lastFrame()).toContain('Watch Mode');
21
+ expect(lastFrame()).toContain('No templates found');
22
+ });
23
+ test('renders with templates', async () => {
24
+ const { lastFrame } = render(React.createElement(Watch, null));
25
+ await wait(100); // Allow time for UI to render and DB to initialize
26
+ expect(lastFrame()).toContain('Watch Mode');
21
27
  });
22
28
  });
23
29
  //# sourceMappingURL=watch.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watch.test.js","sourceRoot":"","sources":["../../../src/__tests__/watch.test.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC7C,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAChE,OAAO,KAAK,MAAM,sBAAsB,CAAC;AAEzC,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,EAAC,cAAc,EAAC,EAAE;IACpC,MAAM,MAAM,GAAG,CAAC,MAAM,cAAc,EAAE,CAAyB,CAAC;IAChE,OAAO;QACL,GAAG,MAAM;QACT,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC;KAClC,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,MAAM,IAAI,GAAG,CAAC,EAAU,EAAE,EAAE,CAAC,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAE7E,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,UAAU,CAAC,GAAG,EAAE;QACd,EAAE,CAAC,aAAa,EAAE,CAAC;IACrB,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,yCAAyC,EAAE,KAAK,IAAI,EAAE;QACzD,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,CAAC,oBAAC,KAAK,OAAG,CAAC,CAAC;QACxC,MAAM,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,mDAAmD;QAEpE,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;QAC5C,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,SAAS,CAAC,oBAAoB,CAAC,CAAC;IACtD,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,wBAAwB,EAAE,KAAK,IAAI,EAAE;QACxC,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,CAAC,oBAAC,KAAK,OAAG,CAAC,CAAC;QACxC,MAAM,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,mDAAmD;QAEpE,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;IAC9C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli.tsx"],"names":[],"mappings":";AACA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,cAAc,MAAM,iBAAiB,CAAC;AAC7C,OAAO,WAAW,MAAM,iBAAiB,CAAC,SAAS,IAAI,EAAE,MAAM,EAAE,CAAC;AAElE,cAAc,CAAC,EAAE,GAAG,EAAE,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC;AAE9C,MAAM,GAAG,GAAG,IAAI,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC,IAAI;CACxB,CAAC,CAAC;AAEH,MAAM,GAAG,CAAC,GAAG,EAAE,CAAC"}