@t1mmen/srtd 0.2.2 → 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 -89
- 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 +12 -7
- 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,24 +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
|
-
|
|
40
|
-
#
|
|
41
|
-
|
|
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
|
|
42
51
|
```
|
|
43
52
|
|
|
44
|
-
|
|
53
|
+
### Setup
|
|
45
54
|
|
|
46
55
|
```bash
|
|
47
56
|
cd your-supabase-project
|
|
48
57
|
srtd init
|
|
49
58
|
```
|
|
50
59
|
|
|
51
|
-
Create
|
|
60
|
+
### Create Your First Template
|
|
61
|
+
|
|
62
|
+
Create `supabase/migrations-templates/my_function.sql`:
|
|
52
63
|
|
|
53
64
|
```sql
|
|
54
65
|
CREATE OR REPLACE FUNCTION my_function()
|
|
@@ -59,21 +70,23 @@ END;
|
|
|
59
70
|
$$ LANGUAGE plpgsql;
|
|
60
71
|
```
|
|
61
72
|
|
|
62
|
-
|
|
73
|
+
### Development Workflow
|
|
63
74
|
|
|
75
|
+
1. Start watch mode:
|
|
64
76
|
```bash
|
|
65
77
|
srtd watch # Changes auto-apply to local database
|
|
66
78
|
```
|
|
67
79
|
|
|
68
|
-
When ready to deploy:
|
|
69
|
-
|
|
80
|
+
2. When ready to deploy:
|
|
70
81
|
```bash
|
|
71
|
-
srtd build
|
|
72
|
-
supabase
|
|
82
|
+
srtd build # Creates timestamped migration file
|
|
83
|
+
supabase migration up # Apply using Supabase CLI
|
|
73
84
|
```
|
|
74
85
|
|
|
75
86
|
## Commands 🎮
|
|
76
87
|
|
|
88
|
+
### Interactive Mode
|
|
89
|
+
|
|
77
90
|
Running `srtd` without arguments opens an interactive menu:
|
|
78
91
|
|
|
79
92
|
```
|
|
@@ -83,68 +96,101 @@ Running `srtd` without arguments opens an interactive menu:
|
|
|
83
96
|
👀 watch - Watch templates for changes, apply directly to database
|
|
84
97
|
```
|
|
85
98
|
|
|
86
|
-
|
|
99
|
+
### CLI Mode
|
|
87
100
|
|
|
88
|
-
- 🏗️ `build` - Generate
|
|
89
|
-
- ▶️ `apply` - Apply templates directly to local database
|
|
90
|
-
- ✍️ `register [file.sql]` - Mark templates as already built
|
|
91
|
-
- 👀 `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!
|
|
92
108
|
|
|
93
109
|
## Perfect For 🎯
|
|
94
110
|
|
|
95
|
-
Ideal
|
|
111
|
+
### Ideal Use Cases
|
|
96
112
|
|
|
97
|
-
✅
|
|
113
|
+
✅ Database functions:
|
|
98
114
|
```sql
|
|
99
|
-
|
|
100
|
-
|
|
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 $$
|
|
101
124
|
BEGIN
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
p.description || ' ' ||
|
|
108
|
-
p.tags || ' ' ||
|
|
109
|
-
COALESCE((
|
|
110
|
-
SELECT string_agg(c.name, ' ')
|
|
111
|
-
FROM categories c
|
|
112
|
-
WHERE c.id = ANY(p.category_ids)
|
|
113
|
-
), '')
|
|
114
|
-
) @@ plainto_tsquery('english', query)
|
|
115
|
-
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;
|
|
116
130
|
END;
|
|
117
131
|
$$ LANGUAGE plpgsql;
|
|
118
132
|
```
|
|
119
133
|
|
|
120
|
-
✅ 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:
|
|
121
146
|
```sql
|
|
122
|
-
CREATE
|
|
123
|
-
|
|
124
|
-
|
|
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;
|
|
125
156
|
```
|
|
126
157
|
|
|
127
|
-
✅ Roles and
|
|
158
|
+
✅ Roles and Permissions:
|
|
128
159
|
```sql
|
|
129
|
-
|
|
160
|
+
-- Revoke all first for clean state
|
|
161
|
+
REVOKE ALL ON ALL TABLES IN SCHEMA public FROM public;
|
|
162
|
+
|
|
163
|
+
-- Grant specific access
|
|
130
164
|
GRANT USAGE ON SCHEMA public TO authenticated;
|
|
131
165
|
GRANT SELECT ON ALL TABLES IN SCHEMA public TO authenticated;
|
|
132
166
|
```
|
|
133
167
|
|
|
134
|
-
|
|
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
|
|
135
183
|
|
|
136
184
|
* ❌ Table structures
|
|
137
185
|
* ❌ Indexes
|
|
138
186
|
* ❌ Data modifications
|
|
139
|
-
* ❌
|
|
187
|
+
* ❌ Non-idempotent operations
|
|
140
188
|
|
|
141
|
-
|
|
189
|
+
Use regular [Supabase](https://supabase.com) migrations for these cases.
|
|
142
190
|
|
|
143
191
|
## The Power of Templates 💪
|
|
144
192
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
With templates, the change is clear and reviewable:
|
|
193
|
+
Templates make code reviews meaningful. Consider this PR adding priority to a notification function:
|
|
148
194
|
|
|
149
195
|
```diff
|
|
150
196
|
CREATE OR REPLACE FUNCTION dispatch_notification(
|
|
@@ -181,23 +227,37 @@ CREATE OR REPLACE FUNCTION dispatch_notification(
|
|
|
181
227
|
RETURNING id INTO notification_id;
|
|
182
228
|
```
|
|
183
229
|
|
|
184
|
-
Without templates,
|
|
230
|
+
Without templates, this would appear as a complete rewrite in your PR.
|
|
185
231
|
|
|
186
232
|
## Configuration 📝
|
|
187
233
|
|
|
188
|
-
|
|
234
|
+
`srtd.config.json` created during initialization:
|
|
189
235
|
|
|
190
|
-
```
|
|
236
|
+
```jsonc
|
|
191
237
|
{
|
|
238
|
+
// Prevents building templates with this extension
|
|
192
239
|
"wipIndicator": ".wip",
|
|
240
|
+
|
|
241
|
+
// Migration file naming: 20211001000000_srtd-my_function.sql
|
|
242
|
+
"migrationPrefix": "srtd",
|
|
243
|
+
|
|
244
|
+
// Template discovery
|
|
193
245
|
"filter": "**/*.sql",
|
|
246
|
+
|
|
247
|
+
// Migration file comments
|
|
194
248
|
"banner": "You very likely **DO NOT** want to manually edit this generated file.",
|
|
195
249
|
"footer": "",
|
|
250
|
+
|
|
251
|
+
// Wrap migrations in transaction
|
|
196
252
|
"wrapInTransaction": true,
|
|
253
|
+
|
|
254
|
+
// File paths
|
|
197
255
|
"templateDir": "supabase/migrations-templates",
|
|
198
256
|
"migrationDir": "supabase/migrations",
|
|
199
257
|
"buildLog": "supabase/migrations-templates/.buildlog.json",
|
|
200
258
|
"localBuildLog": "supabase/migrations-templates/.buildlog.local.json",
|
|
259
|
+
|
|
260
|
+
// Database connection
|
|
201
261
|
"pgConnection": "postgresql://postgres:postgres@localhost:54322/postgres"
|
|
202
262
|
}
|
|
203
263
|
```
|
|
@@ -206,74 +266,73 @@ During initialization, `srtd` creates a `srtd.config.json`:
|
|
|
206
266
|
|
|
207
267
|
### Work in Progress Templates
|
|
208
268
|
|
|
209
|
-
Add `.wip.sql` extension to
|
|
210
|
-
|
|
269
|
+
Add `.wip.sql` extension to prevent migration generation:
|
|
211
270
|
```bash
|
|
212
|
-
my_function.wip.sql #
|
|
271
|
+
my_function.wip.sql # Only applied locally, never built
|
|
213
272
|
```
|
|
214
273
|
|
|
215
274
|
### Template State Management
|
|
216
275
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
- `.buildlog.json` -
|
|
220
|
-
- `.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`)
|
|
221
279
|
|
|
222
280
|
### Register Existing Objects
|
|
223
281
|
|
|
224
|
-
Import existing database objects
|
|
225
|
-
|
|
282
|
+
Import existing database objects:
|
|
226
283
|
```bash
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
284
|
+
# Register specific template
|
|
285
|
+
srtd register my_function.sql
|
|
286
|
+
|
|
287
|
+
# Interactive multi-select UI
|
|
288
|
+
srtd register
|
|
230
289
|
```
|
|
231
290
|
|
|
232
291
|
## Development 🛠️
|
|
233
292
|
|
|
234
|
-
|
|
293
|
+
### Local Setup
|
|
235
294
|
|
|
236
|
-
1. Set up the development environment:
|
|
237
295
|
```bash
|
|
296
|
+
# Clone and install
|
|
238
297
|
git clone https://github.com/stokke/srtd.git
|
|
239
298
|
cd srtd
|
|
240
299
|
npm install
|
|
241
|
-
```
|
|
242
300
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
npm
|
|
246
|
-
npm
|
|
247
|
-
npm start # Builds, links, and runs CLI
|
|
248
|
-
```
|
|
301
|
+
# Development
|
|
302
|
+
npm run dev # Watch mode
|
|
303
|
+
npm test # Run tests
|
|
304
|
+
npm start # Build, link, run
|
|
249
305
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
npm run
|
|
253
|
-
npm run
|
|
254
|
-
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
|
|
255
310
|
```
|
|
256
311
|
|
|
257
312
|
## Contributing 🤝
|
|
258
313
|
|
|
259
|
-
|
|
314
|
+
While feature-complete for our needs, we welcome:
|
|
260
315
|
|
|
261
|
-
- Bug fixes and reliability improvements
|
|
262
|
-
- Documentation improvements
|
|
263
|
-
- Test coverage
|
|
264
|
-
- Performance optimizations
|
|
316
|
+
- 🐛 Bug fixes and reliability improvements
|
|
317
|
+
- 📚 Documentation improvements
|
|
318
|
+
- ✅ Test coverage enhancements
|
|
319
|
+
- ⚡️ Performance optimizations
|
|
265
320
|
|
|
266
|
-
### Contribution
|
|
321
|
+
### Contribution Process
|
|
267
322
|
|
|
268
323
|
1. Create a [changeset](https://github.com/changesets/changesets) (`npm run changeset`)
|
|
269
324
|
2. Ensure tests pass (`npm test`)
|
|
270
325
|
3. Follow existing code style
|
|
271
|
-
4. Update documentation
|
|
326
|
+
4. Update documentation
|
|
272
327
|
|
|
273
|
-
Note
|
|
328
|
+
Note: New features are evaluated based on alignment with project scope.
|
|
274
329
|
|
|
275
330
|
## License
|
|
276
331
|
|
|
277
|
-
|
|
332
|
+
MIT License - see [LICENSE](LICENSE) file.
|
|
333
|
+
|
|
334
|
+
---
|
|
278
335
|
|
|
279
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"}
|