@brandtg/flapjack 0.1.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/LICENSE +27 -0
- package/README.md +584 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +356 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/migrate.d.ts +49 -0
- package/dist/migrate.d.ts.map +1 -0
- package/dist/migrate.js +55 -0
- package/dist/model.d.ts +212 -0
- package/dist/model.d.ts.map +1 -0
- package/dist/model.js +365 -0
- package/dist/types.d.ts +26 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/migrations/1759720355601_create-feature-flag.js +65 -0
- package/package.json +80 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Copyright (c) 2025 Greg Brandt
|
|
2
|
+
All rights reserved.
|
|
3
|
+
|
|
4
|
+
Redistribution and use in source and binary forms, with or without modification,
|
|
5
|
+
are permitted provided that the following conditions are met:
|
|
6
|
+
|
|
7
|
+
1. Redistributions of source code must retain the above copyright notice,
|
|
8
|
+
this list of conditions and the following disclaimer.
|
|
9
|
+
|
|
10
|
+
2. Redistributions in binary form must reproduce the above copyright
|
|
11
|
+
notice, this list of conditions and the following disclaimer in the
|
|
12
|
+
documentation and/or other materials provided with the distribution.
|
|
13
|
+
|
|
14
|
+
3. Neither the name of Flapjack nor the names of its contributors may
|
|
15
|
+
be used to endorse or promote products derived from this software
|
|
16
|
+
without specific prior written permission.
|
|
17
|
+
|
|
18
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
19
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
20
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
21
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
|
22
|
+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
23
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
24
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
25
|
+
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
26
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
27
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.md
ADDED
|
@@ -0,0 +1,584 @@
|
|
|
1
|
+
# Flapjack
|
|
2
|
+
|
|
3
|
+
[](https://github.com/brandtg/flapjack/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/@brandtg/flapjack)
|
|
5
|
+
[](https://opensource.org/licenses/BSD-3-Clause)
|
|
6
|
+
|
|
7
|
+
A simple feature flags library with PostgreSQL integration, inspired by [django-waffle](https://github.com/django-waffle/django-waffle).
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **Multiple targeting strategies**: Enable features for specific users, roles, groups, or percentage rollouts
|
|
12
|
+
- **Consistent hashing**: Deterministic user bucketing for A/B testing and experimentation
|
|
13
|
+
- **PostgreSQL-backed**: Reliable, transactional flag storage with your existing database
|
|
14
|
+
- **CLI included**: Manage feature flags from the command line
|
|
15
|
+
- **TypeScript-first**: Full type safety with comprehensive TypeScript definitions
|
|
16
|
+
- **Battle-tested**: Comprehensive test suite with 31+ tests
|
|
17
|
+
|
|
18
|
+
## Requirements
|
|
19
|
+
|
|
20
|
+
- Node.js >= 18.0.0
|
|
21
|
+
- PostgreSQL >= 10.0
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
Install the library
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install @brandtg/flapjack
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Apply the migrations
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
import { runMigrations } from "@brandtg/flapjack";
|
|
35
|
+
|
|
36
|
+
await runMigrations({
|
|
37
|
+
// Connect to your PostgreSQL database
|
|
38
|
+
databaseUrl: process.env.DATABASE_URL,
|
|
39
|
+
// Use the same migrations table as your application
|
|
40
|
+
migrationsTable: "pgmigrations",
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Usage
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import { Pool } from "pg";
|
|
48
|
+
import { FeatureFlagModel } from "@brandtg/flapjack";
|
|
49
|
+
|
|
50
|
+
// Connect to the database
|
|
51
|
+
const pool: Pool = getDatabasePool();
|
|
52
|
+
const featureFlags = new FeatureFlagModel(pool);
|
|
53
|
+
|
|
54
|
+
// Create a feature flag
|
|
55
|
+
// N.b. use a meaningful naming scheme like <feature>_<date>_<owner>
|
|
56
|
+
const flag = await featureFlags.create({
|
|
57
|
+
name: "enable_new_checkout_20250101_gbrandt",
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Check if a feature flag is active for a user
|
|
61
|
+
const active = await featureFlag.isActiveForUser({
|
|
62
|
+
name: "enable_new_checkout_20250101_gbrandt",
|
|
63
|
+
user: "1234",
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Enable the feature flag for certain roles
|
|
67
|
+
await featureFlags.update(flag.id, {
|
|
68
|
+
roles: ["admin", "staff"],
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Enable the feature flag for certain user groups
|
|
72
|
+
await featureFlags.update(flag.id, {
|
|
73
|
+
groups: ["early_adopters"],
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Launch the flag to a certain percentage of users
|
|
77
|
+
await featureFlags.update(flag.id, {
|
|
78
|
+
percent: 25,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// Launch the flag to everyone
|
|
82
|
+
await featureFlags.update(flag.id, {
|
|
83
|
+
everyone: true,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// Disable the flag for everyone
|
|
87
|
+
await featureFlags.update(flag.id, {
|
|
88
|
+
everyone: false,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Move the flag back into normal state (other rules then apply)
|
|
92
|
+
await featureFlags.update(flag.id, {
|
|
93
|
+
everyone: null,
|
|
94
|
+
});
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## How Feature Flag Evaluation Works
|
|
98
|
+
|
|
99
|
+
When checking if a feature flag is active for a user, Flapjack evaluates rules in the following order:
|
|
100
|
+
|
|
101
|
+
1. **Everyone Override** (`everyone: true/false`): If set, immediately returns this value, ignoring all other rules
|
|
102
|
+
2. **User List** (`users: [...]`): If the user ID is in this list, returns `true`
|
|
103
|
+
3. **Group Membership** (`groups: [...]`): If the user belongs to any specified group, returns `true`
|
|
104
|
+
4. **Role Membership** (`roles: [...]`): If the user has any specified role, returns `true`
|
|
105
|
+
5. **Percentage Rollout** (`percent: 0-99.9`): Uses consistent hashing to deterministically bucket users
|
|
106
|
+
6. **Default**: Returns `false` if no conditions are met
|
|
107
|
+
|
|
108
|
+
### Example Evaluation
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
// Flag configured with multiple rules
|
|
112
|
+
await featureFlags.create({
|
|
113
|
+
name: "new_feature",
|
|
114
|
+
roles: ["admin"],
|
|
115
|
+
groups: ["beta_testers"],
|
|
116
|
+
percent: 25,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// Admin user: ✓ enabled (matches role)
|
|
120
|
+
await featureFlags.isActiveForUser({
|
|
121
|
+
name: "new_feature",
|
|
122
|
+
user: "user_123",
|
|
123
|
+
roles: ["admin"],
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// Beta tester: ✓ enabled (matches group)
|
|
127
|
+
await featureFlags.isActiveForUser({
|
|
128
|
+
name: "new_feature",
|
|
129
|
+
user: "user_456",
|
|
130
|
+
groups: ["beta_testers"],
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Regular user: ? maybe (depends on hash bucket)
|
|
134
|
+
await featureFlags.isActiveForUser({
|
|
135
|
+
name: "new_feature",
|
|
136
|
+
user: "user_789",
|
|
137
|
+
roles: ["user"],
|
|
138
|
+
});
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Performance Considerations
|
|
142
|
+
|
|
143
|
+
⚠️ **Important**: Flapjack queries the database on every `isActiveForUser()` call. For high-traffic applications, consider:
|
|
144
|
+
|
|
145
|
+
### Recommended Caching Strategy
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
import { Pool } from "pg";
|
|
149
|
+
import { FeatureFlagModel } from "@brandtg/flapjack";
|
|
150
|
+
|
|
151
|
+
// Simple in-memory cache with TTL
|
|
152
|
+
class CachedFeatureFlags {
|
|
153
|
+
private model: FeatureFlagModel;
|
|
154
|
+
private cache = new Map<string, { flag: any; expires: number }>();
|
|
155
|
+
private ttlMs = 60000; // 1 minute
|
|
156
|
+
|
|
157
|
+
constructor(pool: Pool) {
|
|
158
|
+
this.model = new FeatureFlagModel(pool);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async isActiveForUser(params: {
|
|
162
|
+
name: string;
|
|
163
|
+
user?: string;
|
|
164
|
+
roles?: string[];
|
|
165
|
+
groups?: string[];
|
|
166
|
+
}): Promise<boolean> {
|
|
167
|
+
const now = Date.now();
|
|
168
|
+
const cached = this.cache.get(params.name);
|
|
169
|
+
|
|
170
|
+
// Use cached flag if still valid
|
|
171
|
+
if (cached && cached.expires > now) {
|
|
172
|
+
// Re-evaluate with cached flag data
|
|
173
|
+
return this.evaluateLocally(cached.flag, params);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Cache miss or expired - fetch from DB
|
|
177
|
+
return await this.model.isActiveForUser(params);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
private evaluateLocally(flag: any, params: any): boolean {
|
|
181
|
+
// Implement evaluation logic locally to avoid DB queries
|
|
182
|
+
// See model.ts isActiveForUser() for reference
|
|
183
|
+
// ...
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Database Connection Pooling
|
|
189
|
+
|
|
190
|
+
Always use connection pooling in production:
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
import { Pool } from "pg";
|
|
194
|
+
|
|
195
|
+
const pool = new Pool({
|
|
196
|
+
connectionString: process.env.DATABASE_URL,
|
|
197
|
+
max: 20, // Maximum pool size
|
|
198
|
+
idleTimeoutMillis: 30000,
|
|
199
|
+
connectionTimeoutMillis: 2000,
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
const featureFlags = new FeatureFlagModel(pool);
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Performance Tips
|
|
206
|
+
|
|
207
|
+
- **Cache flag configurations** at the application level with a reasonable TTL (30-60 seconds)
|
|
208
|
+
- **Batch flag checks** when possible to reduce round trips
|
|
209
|
+
- **Use database indexes**: The default migration includes an index on `name` field
|
|
210
|
+
- **Monitor query performance**: Feature flag checks should be <10ms in most cases
|
|
211
|
+
- **Consider read replicas** for extremely high-traffic scenarios
|
|
212
|
+
|
|
213
|
+
## Experimentation and A/B Testing
|
|
214
|
+
|
|
215
|
+
Flapjack's percentage rollout feature enables experimentation and A/B testing:
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
// Create an experiment: 50% of users see the new feature
|
|
219
|
+
await featureFlags.create({
|
|
220
|
+
name: "checkout_redesign_experiment",
|
|
221
|
+
percent: 50,
|
|
222
|
+
note: "A/B test: new checkout flow vs. old",
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
// Users are consistently bucketed - same result every time
|
|
226
|
+
const isInExperiment = await featureFlags.isActiveForUser({
|
|
227
|
+
name: "checkout_redesign_experiment",
|
|
228
|
+
user: "user_123",
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// Gradual rollout: Start at 5%, increase to 100% over time
|
|
232
|
+
await featureFlags.update(flagId, { percent: 5 });
|
|
233
|
+
// ... monitor metrics ...
|
|
234
|
+
await featureFlags.update(flagId, { percent: 25 });
|
|
235
|
+
// ... monitor metrics ...
|
|
236
|
+
await featureFlags.update(flagId, { percent: 100 });
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### How User Bucketing Works
|
|
240
|
+
|
|
241
|
+
- Uses **MurmurHash3** for consistent, deterministic hashing
|
|
242
|
+
- Same user ID always maps to the same bucket (0-99)
|
|
243
|
+
- Changing the user ID will change bucket assignment
|
|
244
|
+
- Distribution is uniform across the user base
|
|
245
|
+
|
|
246
|
+
## API Reference
|
|
247
|
+
|
|
248
|
+
### FeatureFlagModel
|
|
249
|
+
|
|
250
|
+
#### `create(input: CreateInput): Promise<FeatureFlag>`
|
|
251
|
+
|
|
252
|
+
Creates a new feature flag.
|
|
253
|
+
|
|
254
|
+
#### `getById(id: number): Promise<FeatureFlag | null>`
|
|
255
|
+
|
|
256
|
+
Retrieves a feature flag by its ID.
|
|
257
|
+
|
|
258
|
+
#### `getByName(name: string): Promise<FeatureFlag | null>`
|
|
259
|
+
|
|
260
|
+
Retrieves a feature flag by its name.
|
|
261
|
+
|
|
262
|
+
#### `list(): Promise<FeatureFlag[]>`
|
|
263
|
+
|
|
264
|
+
Lists all feature flags, ordered by ID.
|
|
265
|
+
|
|
266
|
+
#### `update(id: number, changes: UpdateChanges): Promise<FeatureFlag | null>`
|
|
267
|
+
|
|
268
|
+
Updates a feature flag. Returns the updated flag or null if not found.
|
|
269
|
+
|
|
270
|
+
#### `delete(id: number): Promise<boolean>`
|
|
271
|
+
|
|
272
|
+
Deletes a feature flag. Returns true if deleted, false if not found.
|
|
273
|
+
|
|
274
|
+
#### `isActiveForUser(params): Promise<boolean>`
|
|
275
|
+
|
|
276
|
+
Checks if a feature flag is active for a user based on the evaluation rules.
|
|
277
|
+
|
|
278
|
+
#### `hashUserId(userId: string): Promise<number>`
|
|
279
|
+
|
|
280
|
+
Returns the hash value used for percentage bucketing. Useful for debugging rollout distributions.
|
|
281
|
+
|
|
282
|
+
## CLI Usage
|
|
283
|
+
|
|
284
|
+
Flapjack includes a CLI for managing feature flags:
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
# Set your database URL
|
|
288
|
+
export DATABASE_URL="postgresql://user:pass@localhost/dbname"
|
|
289
|
+
|
|
290
|
+
# Create a flag
|
|
291
|
+
flapjack create --name my_feature --roles admin --note "Admin-only feature"
|
|
292
|
+
|
|
293
|
+
# List all flags
|
|
294
|
+
flapjack list
|
|
295
|
+
|
|
296
|
+
# Get a specific flag
|
|
297
|
+
flapjack get-by-name my_feature
|
|
298
|
+
|
|
299
|
+
# Check if active for a user
|
|
300
|
+
flapjack is-active my_feature --user user123 --roles admin
|
|
301
|
+
|
|
302
|
+
# Update a flag
|
|
303
|
+
flapjack update 1 --percent 50 --everyone false
|
|
304
|
+
|
|
305
|
+
# Clear specific fields
|
|
306
|
+
flapjack update 1 --clear-roles --clear-percent
|
|
307
|
+
|
|
308
|
+
# Delete a flag
|
|
309
|
+
flapjack delete 1
|
|
310
|
+
|
|
311
|
+
# Debug user bucketing
|
|
312
|
+
flapjack hash-user user123
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
## Best Practices
|
|
316
|
+
|
|
317
|
+
### Naming Conventions
|
|
318
|
+
|
|
319
|
+
Use descriptive names that include context:
|
|
320
|
+
|
|
321
|
+
```typescript
|
|
322
|
+
// Good: Includes feature, date, and owner
|
|
323
|
+
"enable_new_checkout_20250101_gbrandt";
|
|
324
|
+
"experiment_ai_suggestions_20250115_team_growth";
|
|
325
|
+
|
|
326
|
+
// Avoid: Too generic
|
|
327
|
+
"new_feature";
|
|
328
|
+
"test_flag";
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
### Gradual Rollouts
|
|
332
|
+
|
|
333
|
+
Always roll out features gradually:
|
|
334
|
+
|
|
335
|
+
1. Start with internal users/roles (e.g., `roles: ["admin", "staff"]`)
|
|
336
|
+
2. Expand to beta testers (e.g., `groups: ["beta_testers"]`)
|
|
337
|
+
3. Percentage rollout (5% → 25% → 50% → 100%)
|
|
338
|
+
4. Enable for everyone (e.g., `everyone: true`)
|
|
339
|
+
5. After stable, remove the flag from code and database
|
|
340
|
+
|
|
341
|
+
### Flag Lifecycle Management
|
|
342
|
+
|
|
343
|
+
```typescript
|
|
344
|
+
// 1. Development: Admin only
|
|
345
|
+
await featureFlags.create({
|
|
346
|
+
name: "new_feature_20250101",
|
|
347
|
+
roles: ["admin"],
|
|
348
|
+
note: "New feature in development",
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
// 2. Beta Testing
|
|
352
|
+
await featureFlags.update(flagId, {
|
|
353
|
+
groups: ["beta_testers"],
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
// 3. Gradual Rollout
|
|
357
|
+
await featureFlags.update(flagId, { percent: 10 });
|
|
358
|
+
// Monitor, then increase...
|
|
359
|
+
|
|
360
|
+
// 4. Full Launch
|
|
361
|
+
await featureFlags.update(flagId, { everyone: true });
|
|
362
|
+
|
|
363
|
+
// 5. Cleanup (after feature is stable)
|
|
364
|
+
// Remove feature flag checks from code
|
|
365
|
+
await featureFlags.delete(flagId);
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
### Error Handling
|
|
369
|
+
|
|
370
|
+
```typescript
|
|
371
|
+
try {
|
|
372
|
+
const isActive = await featureFlags.isActiveForUser({
|
|
373
|
+
name: "my_feature",
|
|
374
|
+
user: "user_123",
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
if (isActive) {
|
|
378
|
+
// Show new feature
|
|
379
|
+
} else {
|
|
380
|
+
// Show old feature
|
|
381
|
+
}
|
|
382
|
+
} catch (error) {
|
|
383
|
+
// On error, fail closed (disable feature) or open (enable feature)
|
|
384
|
+
// depending on your risk tolerance
|
|
385
|
+
console.error("Feature flag check failed:", error);
|
|
386
|
+
const isActive = false; // Fail closed - safer default
|
|
387
|
+
}
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
## Troubleshooting
|
|
391
|
+
|
|
392
|
+
### Database Connection Issues
|
|
393
|
+
|
|
394
|
+
```typescript
|
|
395
|
+
// Verify connection before using feature flags
|
|
396
|
+
import { Pool } from "pg";
|
|
397
|
+
|
|
398
|
+
const pool = new Pool({
|
|
399
|
+
connectionString: process.env.DATABASE_URL,
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
try {
|
|
403
|
+
await pool.query("SELECT 1");
|
|
404
|
+
console.log("Database connection successful");
|
|
405
|
+
} catch (error) {
|
|
406
|
+
console.error("Database connection failed:", error);
|
|
407
|
+
}
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
### Flag Not Found
|
|
411
|
+
|
|
412
|
+
If `isActiveForUser()` returns false unexpectedly:
|
|
413
|
+
|
|
414
|
+
1. Verify the flag exists: `flapjack get-by-name your_flag_name`
|
|
415
|
+
2. Check the evaluation rules with `flapjack is-active`
|
|
416
|
+
3. Verify user/role/group matching is case-sensitive
|
|
417
|
+
|
|
418
|
+
### Percentage Rollout Not Working
|
|
419
|
+
|
|
420
|
+
Debug user bucketing:
|
|
421
|
+
|
|
422
|
+
```bash
|
|
423
|
+
# Check which bucket a user falls into
|
|
424
|
+
flapjack hash-user user_123
|
|
425
|
+
# Output: { userId: "user_123", hash: 1234567, bucket: 67 }
|
|
426
|
+
|
|
427
|
+
# If bucket is 67 and percent is 50, user is NOT in rollout
|
|
428
|
+
# If bucket is 67 and percent is 75, user IS in rollout
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
## Development
|
|
432
|
+
|
|
433
|
+
### Setup
|
|
434
|
+
|
|
435
|
+
Create an environment file:
|
|
436
|
+
|
|
437
|
+
```bash
|
|
438
|
+
npm run dev:env
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
Start the PostgreSQL database:
|
|
442
|
+
|
|
443
|
+
```bash
|
|
444
|
+
npm run dev:docker:up
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
Run database migrations:
|
|
448
|
+
|
|
449
|
+
```bash
|
|
450
|
+
npm run dev:migrate
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
### Running Tests
|
|
454
|
+
|
|
455
|
+
```bash
|
|
456
|
+
npm test
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
### Database Management
|
|
460
|
+
|
|
461
|
+
Create a new migration:
|
|
462
|
+
|
|
463
|
+
```bash
|
|
464
|
+
npm run create-migration -- migration_name
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
Reset the database:
|
|
468
|
+
|
|
469
|
+
```bash
|
|
470
|
+
npm run dev:docker:down
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
## Building and Publishing
|
|
474
|
+
|
|
475
|
+
### Development Build
|
|
476
|
+
|
|
477
|
+
To build the project for development and testing:
|
|
478
|
+
|
|
479
|
+
```bash
|
|
480
|
+
npm run build
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
This compiles TypeScript to JavaScript and generates type declaration files in the `dist/` directory.
|
|
484
|
+
|
|
485
|
+
### Creating a Development Package
|
|
486
|
+
|
|
487
|
+
To create a tarball package that can be installed manually in other projects:
|
|
488
|
+
|
|
489
|
+
```bash
|
|
490
|
+
npm run pack:dev
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
This will create a `flapjack-0.1.0.tgz` file that you can install in another project using:
|
|
494
|
+
|
|
495
|
+
```bash
|
|
496
|
+
npm install /path/to/flapjack-0.1.0.tgz
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
### Publishing to npm
|
|
500
|
+
|
|
501
|
+
Before publishing to npm, make sure your package is ready:
|
|
502
|
+
|
|
503
|
+
1. **Login to npm** (one-time setup):
|
|
504
|
+
|
|
505
|
+
```bash
|
|
506
|
+
npm login
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
This will prompt for your username, password, email, and 2FA code.
|
|
510
|
+
|
|
511
|
+
2. **Verify your login**:
|
|
512
|
+
|
|
513
|
+
```bash
|
|
514
|
+
npm whoami
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
3. **Update the version** in `package.json`:
|
|
518
|
+
|
|
519
|
+
```bash
|
|
520
|
+
# For a patch release (0.1.0 -> 0.1.1)
|
|
521
|
+
npm version patch
|
|
522
|
+
|
|
523
|
+
# For a minor release (0.1.0 -> 0.2.0)
|
|
524
|
+
npm version minor
|
|
525
|
+
|
|
526
|
+
# For a major release (0.1.0 -> 1.0.0)
|
|
527
|
+
npm version major
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
This automatically creates a git commit and tag.
|
|
531
|
+
|
|
532
|
+
4. **Run pre-publish checks** (linting, tests, and build):
|
|
533
|
+
|
|
534
|
+
```bash
|
|
535
|
+
npm run prepublishOnly
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
5. **Publish to npm**:
|
|
539
|
+
|
|
540
|
+
```bash
|
|
541
|
+
npm publish --access public
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
You'll be prompted for your 2FA code. For a dry run to see what would be published:
|
|
545
|
+
|
|
546
|
+
```bash
|
|
547
|
+
npm publish --access public --dry-run
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
6. **Push the version tag to GitHub**:
|
|
551
|
+
|
|
552
|
+
```bash
|
|
553
|
+
git push && git push --tags
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
7. **Optional: Create a GitHub release** for the new version at https://github.com/brandtg/flapjack/releases/new
|
|
557
|
+
|
|
558
|
+
#### Publishing a Beta Version
|
|
559
|
+
|
|
560
|
+
For pre-release versions:
|
|
561
|
+
|
|
562
|
+
```bash
|
|
563
|
+
# Update to a pre-release version
|
|
564
|
+
npm version prerelease --preid=beta
|
|
565
|
+
|
|
566
|
+
# Publish with beta tag
|
|
567
|
+
npm publish --access public --tag beta
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
Users can install beta versions with:
|
|
571
|
+
|
|
572
|
+
```bash
|
|
573
|
+
npm install @brandtg/flapjack@beta
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
## Contributing
|
|
577
|
+
|
|
578
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
579
|
+
|
|
580
|
+
## License
|
|
581
|
+
|
|
582
|
+
This project is licensed under the BSD 3-Clause License - see the [LICENSE](LICENSE) file for details.
|
|
583
|
+
|
|
584
|
+
This project is inspired by [django-waffle](https://github.com/django-waffle/django-waffle).
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|