@quonfig/cli 0.0.1 → 0.0.2
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 +59 -20
- package/dist/commands/init.d.ts +13 -0
- package/dist/commands/init.js +83 -0
- package/dist/init/init-workspace.d.ts +25 -0
- package/dist/init/init-workspace.js +162 -0
- package/dist/init/samples.d.ts +11 -0
- package/dist/init/samples.js +118 -0
- package/dist/init/schema.d.ts +9 -0
- package/dist/init/schema.js +334 -0
- package/dist/init/templates.d.ts +12 -0
- package/dist/init/templates.js +161 -0
- package/dist/util/token-storage.js +6 -1
- package/dist/verify/validate.d.ts +13 -0
- package/dist/verify/validate.js +89 -14
- package/oclif.manifest.json +76 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ $ npm install -g @quonfig/cli
|
|
|
14
14
|
$ qfg COMMAND
|
|
15
15
|
running command...
|
|
16
16
|
$ qfg (--version)
|
|
17
|
-
@quonfig/cli/0.0.
|
|
17
|
+
@quonfig/cli/0.0.2 darwin-arm64 node-v24.4.1
|
|
18
18
|
$ qfg --help [COMMAND]
|
|
19
19
|
USAGE
|
|
20
20
|
$ qfg COMMAND
|
|
@@ -31,6 +31,7 @@ USAGE
|
|
|
31
31
|
* [`qfg generate-new-hex-key`](#qfg-generate-new-hex-key)
|
|
32
32
|
* [`qfg get [NAME]`](#qfg-get-name)
|
|
33
33
|
* [`qfg info [NAME]`](#qfg-info-name)
|
|
34
|
+
* [`qfg init [DIRECTORY]`](#qfg-init-directory)
|
|
34
35
|
* [`qfg interactive`](#qfg-interactive)
|
|
35
36
|
* [`qfg list`](#qfg-list)
|
|
36
37
|
* [`qfg login`](#qfg-login)
|
|
@@ -92,7 +93,7 @@ EXAMPLES
|
|
|
92
93
|
$ qfg create my.new.string --type json --value="{\"key\": \"value\"}"
|
|
93
94
|
```
|
|
94
95
|
|
|
95
|
-
_See code: [src/commands/create.ts](https://github.com/quonfig/cli/blob/v0.0.
|
|
96
|
+
_See code: [src/commands/create.ts](https://github.com/quonfig/cli/blob/v0.0.2/src/commands/create.ts)_
|
|
96
97
|
|
|
97
98
|
## `qfg download`
|
|
98
99
|
|
|
@@ -125,7 +126,7 @@ EXAMPLES
|
|
|
125
126
|
$ qfg download --environment=test --sdk-key=YOUR_SDK_KEY
|
|
126
127
|
```
|
|
127
128
|
|
|
128
|
-
_See code: [src/commands/download.ts](https://github.com/quonfig/cli/blob/v0.0.
|
|
129
|
+
_See code: [src/commands/download.ts](https://github.com/quonfig/cli/blob/v0.0.2/src/commands/download.ts)_
|
|
129
130
|
|
|
130
131
|
## `qfg generate`
|
|
131
132
|
|
|
@@ -195,7 +196,7 @@ EXAMPLES
|
|
|
195
196
|
$ qfg generate --targets node-ts -o ./dist # combine with targets
|
|
196
197
|
```
|
|
197
198
|
|
|
198
|
-
_See code: [src/commands/generate.ts](https://github.com/quonfig/cli/blob/v0.0.
|
|
199
|
+
_See code: [src/commands/generate.ts](https://github.com/quonfig/cli/blob/v0.0.2/src/commands/generate.ts)_
|
|
199
200
|
|
|
200
201
|
## `qfg generate-new-hex-key`
|
|
201
202
|
|
|
@@ -218,7 +219,7 @@ EXAMPLES
|
|
|
218
219
|
$ qfg generate-new-hex-key
|
|
219
220
|
```
|
|
220
221
|
|
|
221
|
-
_See code: [src/commands/generate-new-hex-key.ts](https://github.com/quonfig/cli/blob/v0.0.
|
|
222
|
+
_See code: [src/commands/generate-new-hex-key.ts](https://github.com/quonfig/cli/blob/v0.0.2/src/commands/generate-new-hex-key.ts)_
|
|
222
223
|
|
|
223
224
|
## `qfg get [NAME]`
|
|
224
225
|
|
|
@@ -250,7 +251,7 @@ EXAMPLES
|
|
|
250
251
|
$ qfg get my.config.name --environment=production
|
|
251
252
|
```
|
|
252
253
|
|
|
253
|
-
_See code: [src/commands/get.ts](https://github.com/quonfig/cli/blob/v0.0.
|
|
254
|
+
_See code: [src/commands/get.ts](https://github.com/quonfig/cli/blob/v0.0.2/src/commands/get.ts)_
|
|
254
255
|
|
|
255
256
|
## `qfg info [NAME]`
|
|
256
257
|
|
|
@@ -280,7 +281,45 @@ EXAMPLES
|
|
|
280
281
|
$ qfg info my.config.name
|
|
281
282
|
```
|
|
282
283
|
|
|
283
|
-
_See code: [src/commands/info.ts](https://github.com/quonfig/cli/blob/v0.0.
|
|
284
|
+
_See code: [src/commands/info.ts](https://github.com/quonfig/cli/blob/v0.0.2/src/commands/info.ts)_
|
|
285
|
+
|
|
286
|
+
## `qfg init [DIRECTORY]`
|
|
287
|
+
|
|
288
|
+
Initialize or update a Quonfig workspace
|
|
289
|
+
|
|
290
|
+
```
|
|
291
|
+
USAGE
|
|
292
|
+
$ qfg init [DIRECTORY] [--json] [--interactive] [--no-color] [--verbose] [--dry-run] [--samples]
|
|
293
|
+
|
|
294
|
+
ARGUMENTS
|
|
295
|
+
DIRECTORY [default: .] Target directory (default: current directory)
|
|
296
|
+
|
|
297
|
+
FLAGS
|
|
298
|
+
--dry-run Show what would be done without writing anything
|
|
299
|
+
--[no-]samples Include sample configs (default: yes on first init, no on update)
|
|
300
|
+
|
|
301
|
+
GLOBAL FLAGS
|
|
302
|
+
--[no-]interactive Force interactive mode
|
|
303
|
+
--json Format output as json.
|
|
304
|
+
--no-color Do not colorize output
|
|
305
|
+
--verbose Verbose output
|
|
306
|
+
|
|
307
|
+
DESCRIPTION
|
|
308
|
+
Initialize or update a Quonfig workspace
|
|
309
|
+
|
|
310
|
+
EXAMPLES
|
|
311
|
+
$ qfg init
|
|
312
|
+
|
|
313
|
+
$ qfg init ./my-workspace
|
|
314
|
+
|
|
315
|
+
$ qfg init --no-samples
|
|
316
|
+
|
|
317
|
+
$ qfg init --samples
|
|
318
|
+
|
|
319
|
+
$ qfg init --dry-run
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
_See code: [src/commands/init.ts](https://github.com/quonfig/cli/blob/v0.0.2/src/commands/init.ts)_
|
|
284
323
|
|
|
285
324
|
## `qfg interactive`
|
|
286
325
|
|
|
@@ -298,7 +337,7 @@ EXAMPLES
|
|
|
298
337
|
$ qfg
|
|
299
338
|
```
|
|
300
339
|
|
|
301
|
-
_See code: [src/commands/interactive.ts](https://github.com/quonfig/cli/blob/v0.0.
|
|
340
|
+
_See code: [src/commands/interactive.ts](https://github.com/quonfig/cli/blob/v0.0.2/src/commands/interactive.ts)_
|
|
302
341
|
|
|
303
342
|
## `qfg list`
|
|
304
343
|
|
|
@@ -335,7 +374,7 @@ EXAMPLES
|
|
|
335
374
|
$ qfg list --feature-flags
|
|
336
375
|
```
|
|
337
376
|
|
|
338
|
-
_See code: [src/commands/list.ts](https://github.com/quonfig/cli/blob/v0.0.
|
|
377
|
+
_See code: [src/commands/list.ts](https://github.com/quonfig/cli/blob/v0.0.2/src/commands/list.ts)_
|
|
339
378
|
|
|
340
379
|
## `qfg login`
|
|
341
380
|
|
|
@@ -363,7 +402,7 @@ EXAMPLES
|
|
|
363
402
|
$ qfg login --profile myprofile
|
|
364
403
|
```
|
|
365
404
|
|
|
366
|
-
_See code: [src/commands/login.ts](https://github.com/quonfig/cli/blob/v0.0.
|
|
405
|
+
_See code: [src/commands/login.ts](https://github.com/quonfig/cli/blob/v0.0.2/src/commands/login.ts)_
|
|
367
406
|
|
|
368
407
|
## `qfg logout`
|
|
369
408
|
|
|
@@ -386,7 +425,7 @@ EXAMPLES
|
|
|
386
425
|
$ qfg logout
|
|
387
426
|
```
|
|
388
427
|
|
|
389
|
-
_See code: [src/commands/logout.ts](https://github.com/quonfig/cli/blob/v0.0.
|
|
428
|
+
_See code: [src/commands/logout.ts](https://github.com/quonfig/cli/blob/v0.0.2/src/commands/logout.ts)_
|
|
390
429
|
|
|
391
430
|
## `qfg mcp`
|
|
392
431
|
|
|
@@ -419,7 +458,7 @@ EXAMPLES
|
|
|
419
458
|
$ qfg mcp --url http://local-app.goatsofquonfig.com:3003/api/v1/mcp
|
|
420
459
|
```
|
|
421
460
|
|
|
422
|
-
_See code: [src/commands/mcp.ts](https://github.com/quonfig/cli/blob/v0.0.
|
|
461
|
+
_See code: [src/commands/mcp.ts](https://github.com/quonfig/cli/blob/v0.0.2/src/commands/mcp.ts)_
|
|
423
462
|
|
|
424
463
|
## `qfg override [NAME]`
|
|
425
464
|
|
|
@@ -458,7 +497,7 @@ EXAMPLES
|
|
|
458
497
|
$ qfg override my.double.config --value=3.14159
|
|
459
498
|
```
|
|
460
499
|
|
|
461
|
-
_See code: [src/commands/override.ts](https://github.com/quonfig/cli/blob/v0.0.
|
|
500
|
+
_See code: [src/commands/override.ts](https://github.com/quonfig/cli/blob/v0.0.2/src/commands/override.ts)_
|
|
462
501
|
|
|
463
502
|
## `qfg profile`
|
|
464
503
|
|
|
@@ -481,7 +520,7 @@ EXAMPLES
|
|
|
481
520
|
$ qfg profile
|
|
482
521
|
```
|
|
483
522
|
|
|
484
|
-
_See code: [src/commands/profile.ts](https://github.com/quonfig/cli/blob/v0.0.
|
|
523
|
+
_See code: [src/commands/profile.ts](https://github.com/quonfig/cli/blob/v0.0.2/src/commands/profile.ts)_
|
|
485
524
|
|
|
486
525
|
## `qfg schema NAME`
|
|
487
526
|
|
|
@@ -519,7 +558,7 @@ EXAMPLES
|
|
|
519
558
|
$ qfg schema my-schema --set-json-schema=@schemas/my-schema.json --protected
|
|
520
559
|
```
|
|
521
560
|
|
|
522
|
-
_See code: [src/commands/schema.ts](https://github.com/quonfig/cli/blob/v0.0.
|
|
561
|
+
_See code: [src/commands/schema.ts](https://github.com/quonfig/cli/blob/v0.0.2/src/commands/schema.ts)_
|
|
523
562
|
|
|
524
563
|
## `qfg serve DATA-FILE`
|
|
525
564
|
|
|
@@ -555,7 +594,7 @@ EXAMPLES
|
|
|
555
594
|
$ qfg serve ./quonfig.test.588.config.json --port=3099
|
|
556
595
|
```
|
|
557
596
|
|
|
558
|
-
_See code: [src/commands/serve.ts](https://github.com/quonfig/cli/blob/v0.0.
|
|
597
|
+
_See code: [src/commands/serve.ts](https://github.com/quonfig/cli/blob/v0.0.2/src/commands/serve.ts)_
|
|
559
598
|
|
|
560
599
|
## `qfg set-default [NAME]`
|
|
561
600
|
|
|
@@ -599,7 +638,7 @@ EXAMPLES
|
|
|
599
638
|
$ qfg set-default my.config.name --env-var=MY_ENV_VAR_NAME --environment=production
|
|
600
639
|
```
|
|
601
640
|
|
|
602
|
-
_See code: [src/commands/set-default.ts](https://github.com/quonfig/cli/blob/v0.0.
|
|
641
|
+
_See code: [src/commands/set-default.ts](https://github.com/quonfig/cli/blob/v0.0.2/src/commands/set-default.ts)_
|
|
603
642
|
|
|
604
643
|
## `qfg verify [PATH]`
|
|
605
644
|
|
|
@@ -632,7 +671,7 @@ EXAMPLES
|
|
|
632
671
|
$ qfg verify --json
|
|
633
672
|
```
|
|
634
673
|
|
|
635
|
-
_See code: [src/commands/verify.ts](https://github.com/quonfig/cli/blob/v0.0.
|
|
674
|
+
_See code: [src/commands/verify.ts](https://github.com/quonfig/cli/blob/v0.0.2/src/commands/verify.ts)_
|
|
636
675
|
|
|
637
676
|
## `qfg whoami`
|
|
638
677
|
|
|
@@ -655,7 +694,7 @@ EXAMPLES
|
|
|
655
694
|
$ qfg whoami
|
|
656
695
|
```
|
|
657
696
|
|
|
658
|
-
_See code: [src/commands/whoami.ts](https://github.com/quonfig/cli/blob/v0.0.
|
|
697
|
+
_See code: [src/commands/whoami.ts](https://github.com/quonfig/cli/blob/v0.0.2/src/commands/whoami.ts)_
|
|
659
698
|
|
|
660
699
|
## `qfg workspace`
|
|
661
700
|
|
|
@@ -678,7 +717,7 @@ EXAMPLES
|
|
|
678
717
|
$ qfg workspace
|
|
679
718
|
```
|
|
680
719
|
|
|
681
|
-
_See code: [src/commands/workspace.ts](https://github.com/quonfig/cli/blob/v0.0.
|
|
720
|
+
_See code: [src/commands/workspace.ts](https://github.com/quonfig/cli/blob/v0.0.2/src/commands/workspace.ts)_
|
|
682
721
|
<!-- commandsstop -->
|
|
683
722
|
|
|
684
723
|
## Local Development
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { BaseCommand } from '../index.js';
|
|
2
|
+
export default class Init extends BaseCommand {
|
|
3
|
+
static args: {
|
|
4
|
+
directory: import("@oclif/core/lib/interfaces/parser.js").Arg<string, Record<string, unknown>>;
|
|
5
|
+
};
|
|
6
|
+
static description: string;
|
|
7
|
+
static examples: string[];
|
|
8
|
+
static flags: {
|
|
9
|
+
'dry-run': import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
10
|
+
samples: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
11
|
+
};
|
|
12
|
+
run(): Promise<object>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import { BaseCommand } from '../index.js';
|
|
3
|
+
import { executeInit, planInit } from '../init/init-workspace.js';
|
|
4
|
+
export default class Init extends BaseCommand {
|
|
5
|
+
static args = {
|
|
6
|
+
directory: Args.string({
|
|
7
|
+
default: '.',
|
|
8
|
+
description: 'Target directory (default: current directory)',
|
|
9
|
+
}),
|
|
10
|
+
};
|
|
11
|
+
static description = 'Initialize or update a Quonfig workspace';
|
|
12
|
+
static examples = [
|
|
13
|
+
'<%= config.bin %> init',
|
|
14
|
+
'<%= config.bin %> init ./my-workspace',
|
|
15
|
+
'<%= config.bin %> init --no-samples',
|
|
16
|
+
'<%= config.bin %> init --samples',
|
|
17
|
+
'<%= config.bin %> init --dry-run',
|
|
18
|
+
];
|
|
19
|
+
static flags = {
|
|
20
|
+
'dry-run': Flags.boolean({
|
|
21
|
+
default: false,
|
|
22
|
+
description: 'Show what would be done without writing anything',
|
|
23
|
+
}),
|
|
24
|
+
samples: Flags.boolean({
|
|
25
|
+
allowNo: true,
|
|
26
|
+
description: 'Include sample configs (default: yes on first init, no on update)',
|
|
27
|
+
}),
|
|
28
|
+
};
|
|
29
|
+
async run() {
|
|
30
|
+
const { args, flags } = await this.parse(Init);
|
|
31
|
+
const dir = args.directory;
|
|
32
|
+
const plan = planInit({
|
|
33
|
+
dir,
|
|
34
|
+
dryRun: flags['dry-run'],
|
|
35
|
+
samples: flags.samples,
|
|
36
|
+
});
|
|
37
|
+
const mode = plan.isFirstTime ? 'Initializing' : 'Updating';
|
|
38
|
+
const writeActions = plan.actions.filter((a) => a.kind !== 'skip-file' && a.kind !== 'skip-hook');
|
|
39
|
+
const skipActions = plan.actions.filter((a) => a.kind === 'skip-file' || a.kind === 'skip-hook');
|
|
40
|
+
if (flags['dry-run']) {
|
|
41
|
+
this.log(`${mode} workspace (dry run)\n`);
|
|
42
|
+
if (writeActions.length > 0) {
|
|
43
|
+
this.log('Would perform:');
|
|
44
|
+
for (const action of writeActions) {
|
|
45
|
+
this.log(` + ${action.description}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (skipActions.length > 0) {
|
|
49
|
+
this.log('\nSkipped:');
|
|
50
|
+
for (const action of skipActions) {
|
|
51
|
+
this.log(` - ${action.description}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (writeActions.length === 0) {
|
|
55
|
+
this.log(' Nothing to do.');
|
|
56
|
+
}
|
|
57
|
+
if (this.jsonEnabled()) {
|
|
58
|
+
return { actions: plan.actions, dryRun: true, isFirstTime: plan.isFirstTime, samplesIncluded: plan.samplesIncluded };
|
|
59
|
+
}
|
|
60
|
+
return plan;
|
|
61
|
+
}
|
|
62
|
+
// Execute for real
|
|
63
|
+
executeInit(plan, dir);
|
|
64
|
+
this.log(`${mode} workspace: ${dir === '.' ? process.cwd() : dir}\n`);
|
|
65
|
+
for (const action of writeActions) {
|
|
66
|
+
this.log(` + ${action.description}`);
|
|
67
|
+
}
|
|
68
|
+
for (const action of skipActions) {
|
|
69
|
+
this.log(` - ${action.description}`);
|
|
70
|
+
}
|
|
71
|
+
if (!plan.samplesIncluded && plan.isFirstTime) {
|
|
72
|
+
this.log('\nSample data skipped. Run `qfg init --samples` to add examples.');
|
|
73
|
+
}
|
|
74
|
+
else if (!plan.isFirstTime && !plan.samplesIncluded) {
|
|
75
|
+
this.log('\nDocs updated. Run `qfg init --samples` to add example configs.');
|
|
76
|
+
}
|
|
77
|
+
this.log('\nRun `qfg verify` to validate the workspace.');
|
|
78
|
+
if (this.jsonEnabled()) {
|
|
79
|
+
return { actions: plan.actions, dryRun: false, isFirstTime: plan.isFirstTime, samplesIncluded: plan.samplesIncluded };
|
|
80
|
+
}
|
|
81
|
+
return plan;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core logic for `qfg init`.
|
|
3
|
+
*
|
|
4
|
+
* Uses a plan/execute pattern so --dry-run can show what *would* happen
|
|
5
|
+
* without writing anything.
|
|
6
|
+
*/
|
|
7
|
+
export interface InitOptions {
|
|
8
|
+
dir: string;
|
|
9
|
+
/** true = force include samples, false = force skip, undefined = auto-detect */
|
|
10
|
+
samples: boolean | undefined;
|
|
11
|
+
dryRun: boolean;
|
|
12
|
+
}
|
|
13
|
+
export type ActionKind = 'git-init' | 'create-dir' | 'write-file' | 'skip-file' | 'create-hook' | 'update-hook' | 'skip-hook';
|
|
14
|
+
export interface InitAction {
|
|
15
|
+
kind: ActionKind;
|
|
16
|
+
path: string;
|
|
17
|
+
description: string;
|
|
18
|
+
}
|
|
19
|
+
export interface InitPlan {
|
|
20
|
+
actions: InitAction[];
|
|
21
|
+
isFirstTime: boolean;
|
|
22
|
+
samplesIncluded: boolean;
|
|
23
|
+
}
|
|
24
|
+
export declare function planInit(options: InitOptions): InitPlan;
|
|
25
|
+
export declare function executeInit(plan: InitPlan, dir: string): void;
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core logic for `qfg init`.
|
|
3
|
+
*
|
|
4
|
+
* Uses a plan/execute pattern so --dry-run can show what *would* happen
|
|
5
|
+
* without writing anything.
|
|
6
|
+
*/
|
|
7
|
+
import { execSync } from 'node:child_process';
|
|
8
|
+
import * as fs from 'node:fs';
|
|
9
|
+
import * as path from 'node:path';
|
|
10
|
+
import { SAMPLE_FILES } from './samples.js';
|
|
11
|
+
import { storedConfigJsonSchema } from './schema.js';
|
|
12
|
+
import { PRE_COMMIT_MARKER, agentsMdTemplate, claudeMdTemplate, preCommitHookContent, readmeTemplate, } from './templates.js';
|
|
13
|
+
// ── Directories that define a workspace ────────────────────────────────
|
|
14
|
+
const WORKSPACE_DIRS = [
|
|
15
|
+
'configs',
|
|
16
|
+
'feature-flags',
|
|
17
|
+
'segments',
|
|
18
|
+
'log-levels',
|
|
19
|
+
'schemas',
|
|
20
|
+
];
|
|
21
|
+
// ── Plan ───────────────────────────────────────────────────────────────
|
|
22
|
+
export function planInit(options) {
|
|
23
|
+
const { dir } = options;
|
|
24
|
+
const actions = [];
|
|
25
|
+
// Detect first-time vs update: do any workspace dirs already exist?
|
|
26
|
+
const isFirstTime = !WORKSPACE_DIRS.some((d) => fs.existsSync(path.join(dir, d)));
|
|
27
|
+
// Resolve samples flag: first-time → default on, update → default off
|
|
28
|
+
const includeSamples = options.samples ?? isFirstTime;
|
|
29
|
+
// 0. Ensure it's a git repo
|
|
30
|
+
const gitDir = path.join(dir, '.git');
|
|
31
|
+
const needsGitInit = !fs.existsSync(gitDir);
|
|
32
|
+
if (needsGitInit) {
|
|
33
|
+
actions.push({ kind: 'git-init', path: '.git', description: 'Initialize git repository' });
|
|
34
|
+
}
|
|
35
|
+
// 1. Ensure directories exist
|
|
36
|
+
for (const d of WORKSPACE_DIRS) {
|
|
37
|
+
const dirPath = path.join(dir, d);
|
|
38
|
+
if (fs.existsSync(dirPath)) {
|
|
39
|
+
// directory already exists, nothing to do
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
actions.push({ kind: 'create-dir', path: d, description: `Create ${d}/` });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// 2. Managed files — always overwrite
|
|
46
|
+
const managedFiles = [
|
|
47
|
+
{ file: 'quonfig.schema.json', content: JSON.stringify(storedConfigJsonSchema(), null, 2) + '\n' },
|
|
48
|
+
{ file: 'README.md', content: readmeTemplate() },
|
|
49
|
+
{ file: 'CLAUDE.md', content: claudeMdTemplate() },
|
|
50
|
+
{ file: 'AGENTS.md', content: agentsMdTemplate() },
|
|
51
|
+
];
|
|
52
|
+
for (const { file, content: _content } of managedFiles) {
|
|
53
|
+
const filePath = path.join(dir, file);
|
|
54
|
+
const verb = fs.existsSync(filePath) ? 'Update' : 'Create';
|
|
55
|
+
actions.push({ kind: 'write-file', path: file, description: `${verb} ${file}` });
|
|
56
|
+
}
|
|
57
|
+
// 3. environments.json — create if missing
|
|
58
|
+
const envsPath = path.join(dir, 'environments.json');
|
|
59
|
+
if (fs.existsSync(envsPath)) {
|
|
60
|
+
// leave it alone
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
actions.push({ kind: 'write-file', path: 'environments.json', description: 'Create environments.json' });
|
|
64
|
+
}
|
|
65
|
+
// 4. Sample data
|
|
66
|
+
if (includeSamples) {
|
|
67
|
+
for (const sample of SAMPLE_FILES) {
|
|
68
|
+
const samplePath = path.join(dir, sample.path);
|
|
69
|
+
if (fs.existsSync(samplePath)) {
|
|
70
|
+
actions.push({ kind: 'skip-file', path: sample.path, description: `Skip ${sample.path} (already exists)` });
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
actions.push({ kind: 'write-file', path: sample.path, description: `Create ${sample.path}` });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// 5. Git pre-commit hook (always runs — git repo guaranteed by step 0)
|
|
78
|
+
if (needsGitInit) {
|
|
79
|
+
// Fresh git init — hook will be created
|
|
80
|
+
actions.push({ kind: 'create-hook', path: '.git/hooks/pre-commit', description: 'Install pre-commit hook (qfg verify)' });
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
const hooksDir = path.join(gitDir, 'hooks');
|
|
84
|
+
const hookPath = path.join(hooksDir, 'pre-commit');
|
|
85
|
+
if (fs.existsSync(hookPath)) {
|
|
86
|
+
const existing = fs.readFileSync(hookPath, 'utf-8');
|
|
87
|
+
if (existing.includes(PRE_COMMIT_MARKER)) {
|
|
88
|
+
actions.push({ kind: 'skip-hook', path: '.git/hooks/pre-commit', description: 'Pre-commit hook already installed' });
|
|
89
|
+
}
|
|
90
|
+
else if (existing.includes('qfg verify')) {
|
|
91
|
+
actions.push({ kind: 'skip-hook', path: '.git/hooks/pre-commit', description: 'Pre-commit hook already runs qfg verify' });
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
actions.push({ kind: 'update-hook', path: '.git/hooks/pre-commit', description: 'Append qfg verify to existing pre-commit hook' });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
actions.push({ kind: 'create-hook', path: '.git/hooks/pre-commit', description: 'Install pre-commit hook (qfg verify)' });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return { actions, isFirstTime, samplesIncluded: includeSamples };
|
|
102
|
+
}
|
|
103
|
+
// ── Execute ────────────────────────────────────────────────────────────
|
|
104
|
+
export function executeInit(plan, dir) {
|
|
105
|
+
// Build a lookup for managed file content
|
|
106
|
+
const managedContent = {
|
|
107
|
+
'quonfig.schema.json': JSON.stringify(storedConfigJsonSchema(), null, 2) + '\n',
|
|
108
|
+
'README.md': readmeTemplate(),
|
|
109
|
+
'CLAUDE.md': claudeMdTemplate(),
|
|
110
|
+
'AGENTS.md': agentsMdTemplate(),
|
|
111
|
+
'environments.json': JSON.stringify({ environments: [] }, null, 2) + '\n',
|
|
112
|
+
};
|
|
113
|
+
// Build a lookup for sample content
|
|
114
|
+
const sampleContent = {};
|
|
115
|
+
for (const sample of SAMPLE_FILES) {
|
|
116
|
+
sampleContent[sample.path] = JSON.stringify(sample.content, null, 2) + '\n';
|
|
117
|
+
}
|
|
118
|
+
for (const action of plan.actions) {
|
|
119
|
+
const fullPath = path.join(dir, action.path);
|
|
120
|
+
switch (action.kind) {
|
|
121
|
+
case 'git-init': {
|
|
122
|
+
execSync('git init', { cwd: dir, stdio: 'pipe' });
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
case 'create-dir': {
|
|
126
|
+
fs.mkdirSync(fullPath, { recursive: true });
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
case 'write-file': {
|
|
130
|
+
// Ensure parent directory exists
|
|
131
|
+
const parentDir = path.dirname(fullPath);
|
|
132
|
+
if (!fs.existsSync(parentDir)) {
|
|
133
|
+
fs.mkdirSync(parentDir, { recursive: true });
|
|
134
|
+
}
|
|
135
|
+
const content = managedContent[action.path] ?? sampleContent[action.path];
|
|
136
|
+
if (content) {
|
|
137
|
+
fs.writeFileSync(fullPath, content, 'utf-8');
|
|
138
|
+
}
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
case 'create-hook': {
|
|
142
|
+
const hooksDir = path.dirname(fullPath);
|
|
143
|
+
if (!fs.existsSync(hooksDir)) {
|
|
144
|
+
fs.mkdirSync(hooksDir, { recursive: true });
|
|
145
|
+
}
|
|
146
|
+
fs.writeFileSync(fullPath, preCommitHookContent(), 'utf-8');
|
|
147
|
+
fs.chmodSync(fullPath, 0o755);
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
case 'update-hook': {
|
|
151
|
+
const existing = fs.readFileSync(fullPath, 'utf-8');
|
|
152
|
+
const appended = existing.trimEnd() + '\n\n' + preCommitHookContent().split('\n').slice(1).join('\n');
|
|
153
|
+
fs.writeFileSync(fullPath, appended, 'utf-8');
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
case 'skip-file':
|
|
157
|
+
case 'skip-hook':
|
|
158
|
+
// Nothing to do
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sample config data for `qfg init --samples`.
|
|
3
|
+
*
|
|
4
|
+
* Each entry maps a relative path (e.g. "configs/example.greeting.json")
|
|
5
|
+
* to the JSON object that should be written there.
|
|
6
|
+
*/
|
|
7
|
+
export interface SampleFile {
|
|
8
|
+
path: string;
|
|
9
|
+
content: object;
|
|
10
|
+
}
|
|
11
|
+
export declare const SAMPLE_FILES: SampleFile[];
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sample config data for `qfg init --samples`.
|
|
3
|
+
*
|
|
4
|
+
* Each entry maps a relative path (e.g. "configs/example.greeting.json")
|
|
5
|
+
* to the JSON object that should be written there.
|
|
6
|
+
*/
|
|
7
|
+
export const SAMPLE_FILES = [
|
|
8
|
+
{
|
|
9
|
+
path: 'configs/example.greeting.json',
|
|
10
|
+
content: {
|
|
11
|
+
key: 'example.greeting',
|
|
12
|
+
type: 'config',
|
|
13
|
+
valueType: 'string',
|
|
14
|
+
sendToClientSdk: true,
|
|
15
|
+
description: 'A greeting message shown to users.',
|
|
16
|
+
default: {
|
|
17
|
+
rules: [
|
|
18
|
+
{
|
|
19
|
+
criteria: [{ operator: 'ALWAYS_TRUE' }],
|
|
20
|
+
value: { type: 'string', value: 'Hello, world!' },
|
|
21
|
+
},
|
|
22
|
+
],
|
|
23
|
+
},
|
|
24
|
+
environments: [
|
|
25
|
+
{
|
|
26
|
+
id: 'production',
|
|
27
|
+
rules: [
|
|
28
|
+
{
|
|
29
|
+
criteria: [{ operator: 'ALWAYS_TRUE' }],
|
|
30
|
+
value: { type: 'string', value: 'Welcome to our app!' },
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
variants: [],
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
path: 'feature-flags/example.dark-mode.json',
|
|
40
|
+
content: {
|
|
41
|
+
key: 'example.dark-mode',
|
|
42
|
+
type: 'feature_flag',
|
|
43
|
+
valueType: 'bool',
|
|
44
|
+
sendToClientSdk: true,
|
|
45
|
+
description: 'Enable dark mode UI.',
|
|
46
|
+
default: {
|
|
47
|
+
rules: [
|
|
48
|
+
{
|
|
49
|
+
criteria: [{ operator: 'ALWAYS_TRUE' }],
|
|
50
|
+
value: { type: 'bool', value: false },
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
environments: [
|
|
55
|
+
{
|
|
56
|
+
id: 'staging',
|
|
57
|
+
rules: [
|
|
58
|
+
{
|
|
59
|
+
criteria: [{ operator: 'ALWAYS_TRUE' }],
|
|
60
|
+
value: { type: 'bool', value: true },
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
variants: [],
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
path: 'segments/example.beta-users.json',
|
|
70
|
+
content: {
|
|
71
|
+
key: 'example.beta-users',
|
|
72
|
+
type: 'segment',
|
|
73
|
+
valueType: 'bool',
|
|
74
|
+
sendToClientSdk: false,
|
|
75
|
+
description: 'Users who opted in to the beta program.',
|
|
76
|
+
default: {
|
|
77
|
+
rules: [
|
|
78
|
+
{
|
|
79
|
+
criteria: [
|
|
80
|
+
{
|
|
81
|
+
operator: 'PROP_IS_ONE_OF',
|
|
82
|
+
propertyName: 'user.beta',
|
|
83
|
+
valueToMatch: { type: 'string_list', value: ['true'] },
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
value: { type: 'bool', value: true },
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
criteria: [{ operator: 'ALWAYS_TRUE' }],
|
|
90
|
+
value: { type: 'bool', value: false },
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
},
|
|
94
|
+
environments: [],
|
|
95
|
+
variants: [],
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
path: 'log-levels/example.app.json',
|
|
100
|
+
content: {
|
|
101
|
+
key: 'example.app',
|
|
102
|
+
type: 'log_level',
|
|
103
|
+
valueType: 'log_level',
|
|
104
|
+
sendToClientSdk: false,
|
|
105
|
+
description: 'Application log level.',
|
|
106
|
+
default: {
|
|
107
|
+
rules: [
|
|
108
|
+
{
|
|
109
|
+
criteria: [{ operator: 'ALWAYS_TRUE' }],
|
|
110
|
+
value: { type: 'log_level', value: 'INFO' },
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
},
|
|
114
|
+
environments: [],
|
|
115
|
+
variants: [],
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
];
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON Schema for Quonfig StoredConfig files.
|
|
3
|
+
*
|
|
4
|
+
* This is the machine-readable schema for files in configs/, feature-flags/,
|
|
5
|
+
* segments/, and log-levels/. Written to `quonfig.schema.json` by `qfg init`.
|
|
6
|
+
*
|
|
7
|
+
* Derived from the Zod schemas in verify/validate.ts — keep in sync.
|
|
8
|
+
*/
|
|
9
|
+
export declare function storedConfigJsonSchema(): object;
|