@t1mmen/srtd 0.2.1 → 0.3.0
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.
- package/README.md +148 -87
- package/dist/__tests__/watch.test.js +0 -1
- package/dist/__tests__/watch.test.js.map +1 -1
- package/dist/cli.js +3 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/_app.js +22 -2
- package/dist/commands/_app.js.map +1 -1
- package/dist/commands/apply.d.ts +13 -1
- package/dist/commands/apply.js +18 -6
- package/dist/commands/apply.js.map +1 -1
- package/dist/commands/build.d.ts +13 -1
- package/dist/commands/build.js +14 -4
- package/dist/commands/build.js.map +1 -1
- package/dist/commands/index.js +20 -10
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/register.js +9 -6
- package/dist/commands/register.js.map +1 -1
- package/dist/commands/watch.js +105 -117
- package/dist/commands/watch.js.map +1 -1
- package/dist/components/Branding.js +5 -4
- package/dist/components/Branding.js.map +1 -1
- package/dist/components/Quittable.d.ts +6 -0
- package/dist/components/Quittable.js +36 -0
- package/dist/components/Quittable.js.map +1 -0
- package/dist/hooks/useDatabaseConnection.d.ts +7 -0
- package/dist/hooks/useDatabaseConnection.js +68 -0
- package/dist/hooks/useDatabaseConnection.js.map +1 -0
- package/dist/hooks/useTemplateManager.d.ts +24 -0
- package/dist/hooks/useTemplateManager.js +141 -0
- package/dist/hooks/useTemplateManager.js.map +1 -0
- package/dist/lib/templateManager.d.ts +3 -3
- package/dist/lib/templateManager.js +24 -17
- package/dist/lib/templateManager.js.map +1 -1
- package/dist/types.d.ts +2 -1
- package/dist/utils/config.js +1 -0
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/databaseConnection.d.ts +5 -1
- package/dist/utils/databaseConnection.js +27 -8
- package/dist/utils/databaseConnection.js.map +1 -1
- package/dist/utils/logger.d.ts +1 -0
- package/package.json +13 -8
- package/dist/commands/help.d.ts +0 -1
- package/dist/commands/help.js +0 -2
- package/dist/commands/help.js.map +0 -1
package/README.md
CHANGED
|
@@ -2,11 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
Live-reloading SQL templates for [Supabase](https://supabase.com) projects. DX supercharged! 🚀
|
|
4
4
|
|
|
5
|
+
[](https://www.npmjs.com/package/@t1mmen/srtd)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
[](https://github.com/t1mmen/srtd/actions/workflows/ci.yml)
|
|
8
|
+
[](https://codecov.io/gh/t1mmen/srtd)
|
|
5
9
|
|
|
6
|
-
`srtd` enhances the [Supabase](https://supabase.com)
|
|
10
|
+
`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.
|
|
7
11
|
|
|
8
12
|
Built specifically for projects using the standard [Supabase](https://supabase.com) stack (but probably works alright for other Postgres-based projects, too).
|
|
9
13
|
|
|
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)**
|
|
15
|
+
|
|
10
16
|
## Why This Exists 🤔
|
|
11
17
|
|
|
12
18
|
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,8 +25,8 @@ After over a year of looking-but-not-finding a better way, I paired up with [Cla
|
|
|
19
25
|
## Key Features ✨
|
|
20
26
|
|
|
21
27
|
- **Live Reload**: Changes to your SQL templates instantly update your local database
|
|
22
|
-
- **Single Source of Truth**: Templates are the source of all (non-mutable) database objects,
|
|
23
|
-
- **
|
|
28
|
+
- **Single Source of Truth**: Templates are the source of all (non-mutable) database objects, improving code-review clarity
|
|
29
|
+
- **Just SQL**: Templates build as standard [Supabase](https://supabase.com) migrations when you're ready to deploy
|
|
24
30
|
- **Developer Friendly**: Interactive CLI with visual feedback for all operations
|
|
25
31
|
|
|
26
32
|
## Requirements
|
|
@@ -31,22 +37,29 @@ After over a year of looking-but-not-finding a better way, I paired up with [Cla
|
|
|
31
37
|
|
|
32
38
|
## Quick Start 🚀
|
|
33
39
|
|
|
34
|
-
|
|
40
|
+
### Installation
|
|
35
41
|
|
|
36
42
|
```bash
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
43
|
+
# Global installation
|
|
44
|
+
npm install -g @t1mmen/srtd
|
|
45
|
+
|
|
46
|
+
# Project installation
|
|
47
|
+
npm install --save-dev @t1mmen/srtd
|
|
48
|
+
|
|
49
|
+
# Or run directly
|
|
50
|
+
npx @t1mmen/srtd
|
|
40
51
|
```
|
|
41
52
|
|
|
42
|
-
|
|
53
|
+
### Setup
|
|
43
54
|
|
|
44
55
|
```bash
|
|
45
56
|
cd your-supabase-project
|
|
46
57
|
srtd init
|
|
47
58
|
```
|
|
48
59
|
|
|
49
|
-
Create
|
|
60
|
+
### Create Your First Template
|
|
61
|
+
|
|
62
|
+
Create `supabase/migrations-templates/my_function.sql`:
|
|
50
63
|
|
|
51
64
|
```sql
|
|
52
65
|
CREATE OR REPLACE FUNCTION my_function()
|
|
@@ -57,21 +70,23 @@ END;
|
|
|
57
70
|
$$ LANGUAGE plpgsql;
|
|
58
71
|
```
|
|
59
72
|
|
|
60
|
-
|
|
73
|
+
### Development Workflow
|
|
61
74
|
|
|
75
|
+
1. Start watch mode:
|
|
62
76
|
```bash
|
|
63
77
|
srtd watch # Changes auto-apply to local database
|
|
64
78
|
```
|
|
65
79
|
|
|
66
|
-
When ready to deploy:
|
|
67
|
-
|
|
80
|
+
2. When ready to deploy:
|
|
68
81
|
```bash
|
|
69
|
-
srtd build
|
|
70
|
-
supabase
|
|
82
|
+
srtd build # Creates timestamped migration file
|
|
83
|
+
supabase migration up # Apply using Supabase CLI
|
|
71
84
|
```
|
|
72
85
|
|
|
73
86
|
## Commands 🎮
|
|
74
87
|
|
|
88
|
+
### Interactive Mode
|
|
89
|
+
|
|
75
90
|
Running `srtd` without arguments opens an interactive menu:
|
|
76
91
|
|
|
77
92
|
```
|
|
@@ -81,68 +96,101 @@ Running `srtd` without arguments opens an interactive menu:
|
|
|
81
96
|
👀 watch - Watch templates for changes, apply directly to database
|
|
82
97
|
```
|
|
83
98
|
|
|
84
|
-
|
|
99
|
+
### CLI Mode
|
|
85
100
|
|
|
86
|
-
- 🏗️ `build` - Generate
|
|
87
|
-
- ▶️ `apply` - Apply templates directly to local database
|
|
88
|
-
- ✍️ `register [file.sql]` - Mark templates as already built
|
|
89
|
-
- 👀 `watch` - Watch
|
|
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
|
|
105
|
+
|
|
106
|
+
> [!IMPORTANT]
|
|
107
|
+
> `watch` and `apply` commands modify your local database directly and don't clean up after themselves. Use with caution!
|
|
90
108
|
|
|
91
109
|
## Perfect For 🎯
|
|
92
110
|
|
|
93
|
-
Ideal
|
|
111
|
+
### Ideal Use Cases
|
|
94
112
|
|
|
95
|
-
✅
|
|
113
|
+
✅ Database functions:
|
|
96
114
|
```sql
|
|
97
|
-
|
|
98
|
-
|
|
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 $$
|
|
99
124
|
BEGIN
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
p.description || ' ' ||
|
|
106
|
-
p.tags || ' ' ||
|
|
107
|
-
COALESCE((
|
|
108
|
-
SELECT string_agg(c.name, ' ')
|
|
109
|
-
FROM categories c
|
|
110
|
-
WHERE c.id = ANY(p.category_ids)
|
|
111
|
-
), '')
|
|
112
|
-
) @@ plainto_tsquery('english', query)
|
|
113
|
-
AND (category_id IS NULL OR pc.category_id = category_id);
|
|
125
|
+
PERFORM pg_notify(
|
|
126
|
+
'changes',
|
|
127
|
+
json_build_object('table', TG_TABLE_NAME, 'id', NEW.id)::text
|
|
128
|
+
);
|
|
129
|
+
RETURN NEW;
|
|
114
130
|
END;
|
|
115
131
|
$$ LANGUAGE plpgsql;
|
|
116
132
|
```
|
|
117
133
|
|
|
118
|
-
✅ Row-Level Security (RLS)
|
|
134
|
+
✅ 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
|
+
));
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
✅ Views for data abstraction:
|
|
119
146
|
```sql
|
|
120
|
-
CREATE
|
|
121
|
-
|
|
122
|
-
|
|
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;
|
|
123
156
|
```
|
|
124
157
|
|
|
125
|
-
✅ Roles and
|
|
158
|
+
✅ Roles and Permissions:
|
|
126
159
|
```sql
|
|
127
|
-
|
|
160
|
+
-- Revoke all first for clean state
|
|
161
|
+
REVOKE ALL ON ALL TABLES IN SCHEMA public FROM public;
|
|
162
|
+
|
|
163
|
+
-- Grant specific access
|
|
128
164
|
GRANT USAGE ON SCHEMA public TO authenticated;
|
|
129
165
|
GRANT SELECT ON ALL TABLES IN SCHEMA public TO authenticated;
|
|
130
166
|
```
|
|
131
167
|
|
|
132
|
-
|
|
168
|
+
✅ 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 $$;
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Not Recommended For
|
|
133
183
|
|
|
134
184
|
* ❌ Table structures
|
|
135
185
|
* ❌ Indexes
|
|
136
186
|
* ❌ Data modifications
|
|
137
|
-
* ❌
|
|
187
|
+
* ❌ Non-idempotent operations
|
|
138
188
|
|
|
139
|
-
|
|
189
|
+
Use regular [Supabase](https://supabase.com) migrations for these cases.
|
|
140
190
|
|
|
141
191
|
## The Power of Templates 💪
|
|
142
192
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
With templates, the change is clear and reviewable:
|
|
193
|
+
Templates make code reviews meaningful. Consider this PR adding priority to a notification function:
|
|
146
194
|
|
|
147
195
|
```diff
|
|
148
196
|
CREATE OR REPLACE FUNCTION dispatch_notification(
|
|
@@ -179,23 +227,37 @@ CREATE OR REPLACE FUNCTION dispatch_notification(
|
|
|
179
227
|
RETURNING id INTO notification_id;
|
|
180
228
|
```
|
|
181
229
|
|
|
182
|
-
Without templates,
|
|
230
|
+
Without templates, this would appear as a complete rewrite in your PR.
|
|
183
231
|
|
|
184
232
|
## Configuration 📝
|
|
185
233
|
|
|
186
|
-
|
|
234
|
+
`srtd.config.json` created during initialization:
|
|
187
235
|
|
|
188
|
-
```
|
|
236
|
+
```jsonc
|
|
189
237
|
{
|
|
238
|
+
// Prevents building templates with this extension
|
|
190
239
|
"wipIndicator": ".wip",
|
|
240
|
+
|
|
241
|
+
// Migration file naming: 20211001000000_srtd-my_function.sql
|
|
242
|
+
"migrationPrefix": "srtd",
|
|
243
|
+
|
|
244
|
+
// Template discovery
|
|
191
245
|
"filter": "**/*.sql",
|
|
246
|
+
|
|
247
|
+
// Migration file comments
|
|
192
248
|
"banner": "You very likely **DO NOT** want to manually edit this generated file.",
|
|
193
249
|
"footer": "",
|
|
250
|
+
|
|
251
|
+
// Wrap migrations in transaction
|
|
194
252
|
"wrapInTransaction": true,
|
|
253
|
+
|
|
254
|
+
// File paths
|
|
195
255
|
"templateDir": "supabase/migrations-templates",
|
|
196
256
|
"migrationDir": "supabase/migrations",
|
|
197
257
|
"buildLog": "supabase/migrations-templates/.buildlog.json",
|
|
198
258
|
"localBuildLog": "supabase/migrations-templates/.buildlog.local.json",
|
|
259
|
+
|
|
260
|
+
// Database connection
|
|
199
261
|
"pgConnection": "postgresql://postgres:postgres@localhost:54322/postgres"
|
|
200
262
|
}
|
|
201
263
|
```
|
|
@@ -204,74 +266,73 @@ During initialization, `srtd` creates a `srtd.config.json`:
|
|
|
204
266
|
|
|
205
267
|
### Work in Progress Templates
|
|
206
268
|
|
|
207
|
-
Add `.wip.sql` extension to
|
|
208
|
-
|
|
269
|
+
Add `.wip.sql` extension to prevent migration generation:
|
|
209
270
|
```bash
|
|
210
|
-
my_function.wip.sql #
|
|
271
|
+
my_function.wip.sql # Only applied locally, never built
|
|
211
272
|
```
|
|
212
273
|
|
|
213
274
|
### Template State Management
|
|
214
275
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
- `.buildlog.json` -
|
|
218
|
-
- `.buildlog.local.json` - Tracks local database state (add to .gitignore)
|
|
276
|
+
Two state tracking files:
|
|
277
|
+
- `.buildlog.json` - Migration build state (commit this)
|
|
278
|
+
- `.buildlog.local.json` - Local database state (add to `.gitignore`)
|
|
219
279
|
|
|
220
280
|
### Register Existing Objects
|
|
221
281
|
|
|
222
|
-
Import existing database objects
|
|
223
|
-
|
|
282
|
+
Import existing database objects:
|
|
224
283
|
```bash
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
284
|
+
# Register specific template
|
|
285
|
+
srtd register my_function.sql
|
|
286
|
+
|
|
287
|
+
# Interactive multi-select UI
|
|
288
|
+
srtd register
|
|
228
289
|
```
|
|
229
290
|
|
|
230
291
|
## Development 🛠️
|
|
231
292
|
|
|
232
|
-
|
|
293
|
+
### Local Setup
|
|
233
294
|
|
|
234
|
-
1. Set up the development environment:
|
|
235
295
|
```bash
|
|
296
|
+
# Clone and install
|
|
236
297
|
git clone https://github.com/stokke/srtd.git
|
|
237
298
|
cd srtd
|
|
238
299
|
npm install
|
|
239
|
-
```
|
|
240
300
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
npm
|
|
244
|
-
npm
|
|
245
|
-
npm start # Builds, links, and runs CLI
|
|
246
|
-
```
|
|
301
|
+
# Development
|
|
302
|
+
npm run dev # Watch mode
|
|
303
|
+
npm test # Run tests
|
|
304
|
+
npm start # Build, link, run
|
|
247
305
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
npm run
|
|
251
|
-
npm run
|
|
252
|
-
npm run test:coverage # Test coverage
|
|
306
|
+
# Quality Checks
|
|
307
|
+
npm run typecheck # Type checking
|
|
308
|
+
npm run lint # Lint and fix
|
|
309
|
+
npm run test:coverage # Test coverage
|
|
253
310
|
```
|
|
254
311
|
|
|
255
312
|
## Contributing 🤝
|
|
256
313
|
|
|
257
|
-
|
|
314
|
+
While feature-complete for our needs, we welcome:
|
|
258
315
|
|
|
259
|
-
- Bug fixes and reliability improvements
|
|
260
|
-
- Documentation improvements
|
|
261
|
-
- Test coverage
|
|
262
|
-
- Performance optimizations
|
|
316
|
+
- 🐛 Bug fixes and reliability improvements
|
|
317
|
+
- 📚 Documentation improvements
|
|
318
|
+
- ✅ Test coverage enhancements
|
|
319
|
+
- ⚡️ Performance optimizations
|
|
263
320
|
|
|
264
|
-
### Contribution
|
|
321
|
+
### Contribution Process
|
|
265
322
|
|
|
266
323
|
1. Create a [changeset](https://github.com/changesets/changesets) (`npm run changeset`)
|
|
267
324
|
2. Ensure tests pass (`npm test`)
|
|
268
325
|
3. Follow existing code style
|
|
269
|
-
4. Update documentation
|
|
326
|
+
4. Update documentation
|
|
270
327
|
|
|
271
|
-
Note
|
|
328
|
+
Note: New features are evaluated based on alignment with project scope.
|
|
272
329
|
|
|
273
330
|
## License
|
|
274
331
|
|
|
275
|
-
|
|
332
|
+
MIT License - see [LICENSE](LICENSE) file.
|
|
333
|
+
|
|
334
|
+
---
|
|
276
335
|
|
|
277
336
|
Made with 🪄 by [Timm Stokke](https://timm.stokke.me) & [Claude Sonnet](https://claude.ai)
|
|
337
|
+
|
|
338
|
+
[](https://www.buymeacoffee.com/t1mmen)
|
|
@@ -18,7 +18,6 @@ describe('Watch Command', () => {
|
|
|
18
18
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
19
19
|
const output = lastFrame();
|
|
20
20
|
expect(output).toContain('Watch Mode');
|
|
21
|
-
expect(output).toContain('Watching for template changes');
|
|
22
21
|
});
|
|
23
22
|
});
|
|
24
23
|
//# sourceMappingURL=watch.test.js.map
|
|
@@ -1 +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,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,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC;QAEvD,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;QAC3B,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;
|
|
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,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,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC;QAEvD,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;QAC3B,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;IACzC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env -S node --no-warnings
|
|
2
2
|
import Pastel from 'pastel';
|
|
3
|
+
import updateNotifier from 'update-notifier';
|
|
4
|
+
import packageJson from '../package.json' assert { type: 'json' };
|
|
5
|
+
updateNotifier({ pkg: packageJson }).notify();
|
|
3
6
|
const app = new Pastel({
|
|
4
7
|
importMeta: import.meta,
|
|
5
8
|
});
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.tsx"],"names":[],"mappings":";AACA,OAAO,MAAM,MAAM,QAAQ,CAAC;
|
|
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"}
|
package/dist/commands/_app.js
CHANGED
|
@@ -1,7 +1,27 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Alert, ThemeProvider, defaultTheme, extendTheme } from '@inkjs/ui';
|
|
2
|
+
import { Box, Static, Text } from 'ink';
|
|
2
3
|
import React from 'react';
|
|
4
|
+
import { useDatabaseConnection } from '../hooks/useDatabaseConnection.js';
|
|
5
|
+
const customTheme = extendTheme(defaultTheme, {
|
|
6
|
+
components: {
|
|
7
|
+
Spinner: {
|
|
8
|
+
styles: {
|
|
9
|
+
frame: () => ({
|
|
10
|
+
color: 'magenta',
|
|
11
|
+
}),
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
});
|
|
3
16
|
export default function App({ Component, commandProps }) {
|
|
4
|
-
|
|
17
|
+
const { error } = useDatabaseConnection();
|
|
18
|
+
return (React.createElement(ThemeProvider, { theme: customTheme },
|
|
19
|
+
!!error && (React.createElement(Static, { items: [error] }, error => (React.createElement(Box, { key: error },
|
|
20
|
+
React.createElement(Alert, { variant: "error" },
|
|
21
|
+
React.createElement(Text, { bold: true, color: "red" },
|
|
22
|
+
"Error:",
|
|
23
|
+
' '),
|
|
24
|
+
error))))),
|
|
5
25
|
React.createElement(Component, { ...commandProps })));
|
|
6
26
|
}
|
|
7
27
|
//# sourceMappingURL=_app.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_app.js","sourceRoot":"","sources":["../../src/commands/_app.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"_app.js","sourceRoot":"","sources":["../../src/commands/_app.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC5E,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAkB,MAAM,KAAK,CAAC;AAExD,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,qBAAqB,EAAE,MAAM,mCAAmC,CAAC;AAE1E,MAAM,WAAW,GAAG,WAAW,CAAC,YAAY,EAAE;IAC5C,UAAU,EAAE;QACV,OAAO,EAAE;YACP,MAAM,EAAE;gBACN,KAAK,EAAE,GAAc,EAAE,CAAC,CAAC;oBACvB,KAAK,EAAE,SAAS;iBACjB,CAAC;aACH;SACF;KACF;CACF,CAAC,CAAC;AAEH,MAAM,CAAC,OAAO,UAAU,GAAG,CAAC,EAAE,SAAS,EAAE,YAAY,EAAY;IAC/D,MAAM,EAAE,KAAK,EAAE,GAAG,qBAAqB,EAAE,CAAC;IAE1C,OAAO,CACL,oBAAC,aAAa,IAAC,KAAK,EAAE,WAAW;QAC9B,CAAC,CAAC,KAAK,IAAI,CACV,oBAAC,MAAM,IAAC,KAAK,EAAE,CAAC,KAAK,CAAC,IACnB,KAAK,CAAC,EAAE,CAAC,CACR,oBAAC,GAAG,IAAC,GAAG,EAAE,KAAK;YACb,oBAAC,KAAK,IAAC,OAAO,EAAC,OAAO;gBACpB,oBAAC,IAAI,IAAC,IAAI,QAAC,KAAK,EAAC,KAAK;;oBACb,GAAG,CACL;gBACN,KAAK,CACA,CACJ,CACP,CACM,CACV;QACD,oBAAC,SAAS,OAAK,YAAY,GAAI,CACjB,CACjB,CAAC;AACJ,CAAC"}
|
package/dist/commands/apply.d.ts
CHANGED
|
@@ -1 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
import zod from 'zod';
|
|
2
|
+
export declare const options: zod.ZodObject<{
|
|
3
|
+
force: zod.ZodBoolean;
|
|
4
|
+
}, "strip", zod.ZodTypeAny, {
|
|
5
|
+
force: boolean;
|
|
6
|
+
}, {
|
|
7
|
+
force: boolean;
|
|
8
|
+
}>;
|
|
9
|
+
type Props = {
|
|
10
|
+
options: zod.infer<typeof options>;
|
|
11
|
+
};
|
|
12
|
+
export default function Apply({ options }: Props): null;
|
|
13
|
+
export {};
|
package/dist/commands/apply.js
CHANGED
|
@@ -1,20 +1,32 @@
|
|
|
1
|
+
// commands/build.tsx
|
|
2
|
+
import { useApp } from 'ink';
|
|
3
|
+
import { option } from 'pastel';
|
|
1
4
|
import React from 'react';
|
|
5
|
+
import zod from 'zod';
|
|
2
6
|
import { TemplateManager } from '../lib/templateManager.js';
|
|
3
|
-
export
|
|
7
|
+
export const options = zod.object({
|
|
8
|
+
force: zod.boolean().describe(option({
|
|
9
|
+
description: 'Force apply of all templates, irrespective of changes',
|
|
10
|
+
alias: 'f',
|
|
11
|
+
})),
|
|
12
|
+
});
|
|
13
|
+
export default function Apply({ options }) {
|
|
14
|
+
const { exit } = useApp();
|
|
4
15
|
React.useEffect(() => {
|
|
5
16
|
async function doApply() {
|
|
6
17
|
try {
|
|
7
18
|
const manager = await TemplateManager.create(process.cwd());
|
|
8
|
-
await manager.processTemplates({ apply: true });
|
|
9
|
-
|
|
19
|
+
await manager.processTemplates({ apply: true, force: options.force });
|
|
20
|
+
exit();
|
|
10
21
|
}
|
|
11
22
|
catch (err) {
|
|
12
|
-
|
|
13
|
-
|
|
23
|
+
if (err instanceof Error) {
|
|
24
|
+
exit(err);
|
|
25
|
+
}
|
|
14
26
|
}
|
|
15
27
|
}
|
|
16
28
|
void doApply();
|
|
17
|
-
}, []);
|
|
29
|
+
}, [exit, options]);
|
|
18
30
|
return null;
|
|
19
31
|
}
|
|
20
32
|
//# sourceMappingURL=apply.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"apply.js","sourceRoot":"","sources":["../../src/commands/apply.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAE5D,MAAM,CAAC,OAAO,UAAU,KAAK;
|
|
1
|
+
{"version":3,"file":"apply.js","sourceRoot":"","sources":["../../src/commands/apply.tsx"],"names":[],"mappings":"AAAA,qBAAqB;AACrB,OAAO,EAAE,MAAM,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAE5D,MAAM,CAAC,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC;IAChC,KAAK,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,QAAQ,CAC3B,MAAM,CAAC;QACL,WAAW,EAAE,uDAAuD;QACpE,KAAK,EAAE,GAAG;KACX,CAAC,CACH;CACF,CAAC,CAAC;AAMH,MAAM,CAAC,OAAO,UAAU,KAAK,CAAC,EAAE,OAAO,EAAS;IAC9C,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,EAAE,CAAC;IAC1B,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;QACnB,KAAK,UAAU,OAAO;YACpB,IAAI,CAAC;gBACH,MAAM,OAAO,GAAG,MAAM,eAAe,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;gBAC5D,MAAM,OAAO,CAAC,gBAAgB,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;gBACtE,IAAI,EAAE,CAAC;YACT,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,IAAI,GAAG,YAAY,KAAK,EAAE,CAAC;oBACzB,IAAI,CAAC,GAAG,CAAC,CAAC;gBACZ,CAAC;YACH,CAAC;QACH,CAAC;QACD,KAAK,OAAO,EAAE,CAAC;IACjB,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;IACpB,OAAO,IAAI,CAAC;AACd,CAAC"}
|
package/dist/commands/build.d.ts
CHANGED
|
@@ -1 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
import zod from 'zod';
|
|
2
|
+
export declare const options: zod.ZodObject<{
|
|
3
|
+
force: zod.ZodBoolean;
|
|
4
|
+
}, "strip", zod.ZodTypeAny, {
|
|
5
|
+
force: boolean;
|
|
6
|
+
}, {
|
|
7
|
+
force: boolean;
|
|
8
|
+
}>;
|
|
9
|
+
type Props = {
|
|
10
|
+
options: zod.infer<typeof options>;
|
|
11
|
+
};
|
|
12
|
+
export default function Build({ options }: Props): null;
|
|
13
|
+
export {};
|
package/dist/commands/build.js
CHANGED
|
@@ -1,15 +1,25 @@
|
|
|
1
1
|
// commands/build.tsx
|
|
2
|
+
import { useApp } from 'ink';
|
|
3
|
+
import { option } from 'pastel';
|
|
2
4
|
import React from 'react';
|
|
5
|
+
import zod from 'zod';
|
|
3
6
|
import { TemplateManager } from '../lib/templateManager.js';
|
|
4
|
-
export
|
|
7
|
+
export const options = zod.object({
|
|
8
|
+
force: zod.boolean().describe(option({
|
|
9
|
+
description: 'Force building of all templates, irrespective of changes',
|
|
10
|
+
alias: 'f',
|
|
11
|
+
})),
|
|
12
|
+
});
|
|
13
|
+
export default function Build({ options }) {
|
|
14
|
+
const { exit } = useApp();
|
|
5
15
|
React.useEffect(() => {
|
|
6
16
|
async function doBuild() {
|
|
7
17
|
const manager = await TemplateManager.create(process.cwd());
|
|
8
|
-
await manager.processTemplates({ generateFiles: true });
|
|
9
|
-
|
|
18
|
+
await manager.processTemplates({ generateFiles: true, force: options.force });
|
|
19
|
+
exit();
|
|
10
20
|
}
|
|
11
21
|
doBuild();
|
|
12
|
-
}, []);
|
|
22
|
+
}, [options, exit]);
|
|
13
23
|
return null;
|
|
14
24
|
}
|
|
15
25
|
//# sourceMappingURL=build.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build.js","sourceRoot":"","sources":["../../src/commands/build.tsx"],"names":[],"mappings":"AAAA,qBAAqB;AACrB,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAE5D,MAAM,CAAC,OAAO,UAAU,KAAK;
|
|
1
|
+
{"version":3,"file":"build.js","sourceRoot":"","sources":["../../src/commands/build.tsx"],"names":[],"mappings":"AAAA,qBAAqB;AACrB,OAAO,EAAE,MAAM,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAE5D,MAAM,CAAC,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC;IAChC,KAAK,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,QAAQ,CAC3B,MAAM,CAAC;QACL,WAAW,EAAE,0DAA0D;QACvE,KAAK,EAAE,GAAG;KACX,CAAC,CACH;CACF,CAAC,CAAC;AAMH,MAAM,CAAC,OAAO,UAAU,KAAK,CAAC,EAAE,OAAO,EAAS;IAC9C,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,EAAE,CAAC;IAC1B,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;QACnB,KAAK,UAAU,OAAO;YACpB,MAAM,OAAO,GAAG,MAAM,eAAe,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;YAC5D,MAAM,OAAO,CAAC,gBAAgB,CAAC,EAAE,aAAa,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;YAC9E,IAAI,EAAE,CAAC;QACT,CAAC;QACD,OAAO,EAAE,CAAC;IACZ,CAAC,EAAE,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC;IAEpB,OAAO,IAAI,CAAC;AACd,CAAC"}
|
package/dist/commands/index.js
CHANGED
|
@@ -1,37 +1,47 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
1
|
+
// commands/index.tsx
|
|
2
|
+
import { Select, Spinner } from '@inkjs/ui';
|
|
3
|
+
import { Box, Text } from 'ink';
|
|
3
4
|
import React from 'react';
|
|
4
5
|
import Branding from '../components/Branding.js';
|
|
6
|
+
import Quittable from '../components/Quittable.js';
|
|
7
|
+
import { useDatabaseConnection } from '../hooks/useDatabaseConnection.js';
|
|
5
8
|
import Apply from './apply.js';
|
|
6
9
|
import Build from './build.js';
|
|
7
10
|
import Register from './register.js';
|
|
8
11
|
import Watch from './watch.js';
|
|
9
12
|
export default function UI() {
|
|
13
|
+
const { error, isChecking, isConnected } = useDatabaseConnection();
|
|
10
14
|
const [selectedCommand, setSelectedCommand] = React.useState(null);
|
|
11
|
-
|
|
15
|
+
const handleOnChange = async (value) => {
|
|
16
|
+
setSelectedCommand(value);
|
|
17
|
+
};
|
|
12
18
|
if (selectedCommand === 'register') {
|
|
13
19
|
return React.createElement(Register, { args: undefined });
|
|
14
20
|
}
|
|
15
21
|
if (selectedCommand === 'apply') {
|
|
16
|
-
return React.createElement(Apply,
|
|
22
|
+
return React.createElement(Apply, { options: { force: false } });
|
|
17
23
|
}
|
|
18
24
|
if (selectedCommand === 'build') {
|
|
19
|
-
return React.createElement(Build,
|
|
25
|
+
return React.createElement(Build, { options: { force: false } });
|
|
20
26
|
}
|
|
21
27
|
if (selectedCommand === 'watch') {
|
|
22
28
|
return React.createElement(Watch, null);
|
|
23
29
|
}
|
|
24
30
|
const menuItems = [
|
|
25
|
-
{ label: '🏗️
|
|
26
|
-
{ label: '▶️
|
|
27
|
-
{ label: '✍️
|
|
31
|
+
{ label: '🏗️ build - Build Supabase migrations from templates', value: 'build' },
|
|
32
|
+
{ label: '▶️ apply - Apply migration templates directly to database', value: 'apply' },
|
|
33
|
+
{ label: '✍️ register - Register templates as already built', value: 'register' },
|
|
28
34
|
{
|
|
29
|
-
label: '👀
|
|
35
|
+
label: '👀 watch - Watch templates for changes and apply directly to database',
|
|
30
36
|
value: 'watch',
|
|
31
37
|
},
|
|
32
38
|
];
|
|
33
39
|
return (React.createElement(Box, { flexDirection: "column" },
|
|
34
40
|
React.createElement(Branding, null),
|
|
35
|
-
React.createElement(
|
|
41
|
+
error ? (React.createElement(Box, { gap: 1 },
|
|
42
|
+
React.createElement(Text, { color: "red", bold: true }, "Error"),
|
|
43
|
+
React.createElement(Text, null, "Check your database connection and try again."))) : (React.createElement(Select, { options: menuItems, isDisabled: !isConnected, onChange: handleOnChange })),
|
|
44
|
+
isChecking ? (React.createElement(Box, { marginTop: 1 },
|
|
45
|
+
React.createElement(Spinner, { label: "Checking database connection..." }))) : (React.createElement(Quittable, null))));
|
|
36
46
|
}
|
|
37
47
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/commands/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/commands/index.tsx"],"names":[],"mappings":"AAAA,qBAAqB;AACrB,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAChC,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,QAAQ,MAAM,2BAA2B,CAAC;AACjD,OAAO,SAAS,MAAM,4BAA4B,CAAC;AACnD,OAAO,EAAE,qBAAqB,EAAE,MAAM,mCAAmC,CAAC;AAC1E,OAAO,KAAK,MAAM,YAAY,CAAC;AAC/B,OAAO,KAAK,MAAM,YAAY,CAAC;AAC/B,OAAO,QAAQ,MAAM,eAAe,CAAC;AACrC,OAAO,KAAK,MAAM,YAAY,CAAC;AAE/B,MAAM,CAAC,OAAO,UAAU,EAAE;IACxB,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,GAAG,qBAAqB,EAAE,CAAC;IACnE,MAAM,CAAC,eAAe,EAAE,kBAAkB,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAgB,IAAI,CAAC,CAAC;IAElF,MAAM,cAAc,GAAG,KAAK,EAAE,KAAa,EAAE,EAAE;QAC7C,kBAAkB,CAAC,KAAK,CAAC,CAAC;IAC5B,CAAC,CAAC;IAEF,IAAI,eAAe,KAAK,UAAU,EAAE,CAAC;QACnC,OAAO,oBAAC,QAAQ,IAAC,IAAI,EAAE,SAAS,GAAI,CAAC;IACvC,CAAC;IAED,IAAI,eAAe,KAAK,OAAO,EAAE,CAAC;QAChC,OAAO,oBAAC,KAAK,IAAC,OAAO,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,GAAI,CAAC;IAC9C,CAAC;IAED,IAAI,eAAe,KAAK,OAAO,EAAE,CAAC;QAChC,OAAO,oBAAC,KAAK,IAAC,OAAO,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,GAAI,CAAC;IAC9C,CAAC;IAED,IAAI,eAAe,KAAK,OAAO,EAAE,CAAC;QAChC,OAAO,oBAAC,KAAK,OAAG,CAAC;IACnB,CAAC;IAED,MAAM,SAAS,GAAG;QAChB,EAAE,KAAK,EAAE,sDAAsD,EAAE,KAAK,EAAE,OAAO,EAAE;QACjF,EAAE,KAAK,EAAE,2DAA2D,EAAE,KAAK,EAAE,OAAO,EAAE;QACtF,EAAE,KAAK,EAAE,mDAAmD,EAAE,KAAK,EAAE,UAAU,EAAE;QACjF;YACE,KAAK,EAAE,uEAAuE;YAC9E,KAAK,EAAE,OAAO;SACf;KACF,CAAC;IAEF,OAAO,CACL,oBAAC,GAAG,IAAC,aAAa,EAAC,QAAQ;QACzB,oBAAC,QAAQ,OAAG;QACX,KAAK,CAAC,CAAC,CAAC,CACP,oBAAC,GAAG,IAAC,GAAG,EAAE,CAAC;YACT,oBAAC,IAAI,IAAC,KAAK,EAAC,KAAK,EAAC,IAAI,kBAEf;YACP,oBAAC,IAAI,wDAAqD,CACtD,CACP,CAAC,CAAC,CAAC,CACF,oBAAC,MAAM,IAAC,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,CAAC,WAAW,EAAE,QAAQ,EAAE,cAAc,GAAI,CACnF;QAEA,UAAU,CAAC,CAAC,CAAC,CACZ,oBAAC,GAAG,IAAC,SAAS,EAAE,CAAC;YACf,oBAAC,OAAO,IAAC,KAAK,EAAC,iCAAiC,GAAG,CAC/C,CACP,CAAC,CAAC,CAAC,CACF,oBAAC,SAAS,OAAG,CACd,CACG,CACP,CAAC;AACJ,CAAC"}
|