@quonfig/cli 0.0.23 → 0.0.24

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 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.23 linux-x64 node-v24.4.1
17
+ @quonfig/cli/0.0.24 linux-x64 node-v24.4.1
18
18
  $ qfg --help [COMMAND]
19
19
  USAGE
20
20
  $ qfg COMMAND
@@ -92,7 +92,7 @@ EXAMPLES
92
92
  $ qfg config-schema --json-schema # full JSON Schema document (copy into your editor)
93
93
  ```
94
94
 
95
- _See code: [src/commands/config-schema.ts](https://github.com/quonfig/cli/blob/v0.0.23/src/commands/config-schema.ts)_
95
+ _See code: [src/commands/config-schema.ts](https://github.com/quonfig/cli/blob/v0.0.24/src/commands/config-schema.ts)_
96
96
 
97
97
  ## `qfg create NAME`
98
98
 
@@ -177,7 +177,7 @@ EXAMPLES
177
177
  $ qfg set-rollout my.new.flag --environment production --true-percent 20
178
178
  ```
179
179
 
180
- _See code: [src/commands/create.ts](https://github.com/quonfig/cli/blob/v0.0.23/src/commands/create.ts)_
180
+ _See code: [src/commands/create.ts](https://github.com/quonfig/cli/blob/v0.0.24/src/commands/create.ts)_
181
181
 
182
182
  ## `qfg delete NAME`
183
183
 
@@ -223,7 +223,7 @@ EXAMPLES
223
223
  $ qfg delete my.config --yes
224
224
  ```
225
225
 
226
- _See code: [src/commands/delete.ts](https://github.com/quonfig/cli/blob/v0.0.23/src/commands/delete.ts)_
226
+ _See code: [src/commands/delete.ts](https://github.com/quonfig/cli/blob/v0.0.24/src/commands/delete.ts)_
227
227
 
228
228
  ## `qfg flag info [NAME]`
229
229
 
@@ -426,7 +426,7 @@ EXAMPLES
426
426
  $ qfg generate --targets node-ts -o ./dist # combine with targets
427
427
  ```
428
428
 
429
- _See code: [src/commands/generate.ts](https://github.com/quonfig/cli/blob/v0.0.23/src/commands/generate.ts)_
429
+ _See code: [src/commands/generate.ts](https://github.com/quonfig/cli/blob/v0.0.24/src/commands/generate.ts)_
430
430
 
431
431
  ## `qfg generate-new-hex-key`
432
432
 
@@ -449,7 +449,7 @@ EXAMPLES
449
449
  $ qfg generate-new-hex-key
450
450
  ```
451
451
 
452
- _See code: [src/commands/generate-new-hex-key.ts](https://github.com/quonfig/cli/blob/v0.0.23/src/commands/generate-new-hex-key.ts)_
452
+ _See code: [src/commands/generate-new-hex-key.ts](https://github.com/quonfig/cli/blob/v0.0.24/src/commands/generate-new-hex-key.ts)_
453
453
 
454
454
  ## `qfg get [NAME]`
455
455
 
@@ -481,7 +481,7 @@ EXAMPLES
481
481
  $ qfg get my.config.name --environment=production
482
482
  ```
483
483
 
484
- _See code: [src/commands/get.ts](https://github.com/quonfig/cli/blob/v0.0.23/src/commands/get.ts)_
484
+ _See code: [src/commands/get.ts](https://github.com/quonfig/cli/blob/v0.0.24/src/commands/get.ts)_
485
485
 
486
486
  ## `qfg info [NAME]`
487
487
 
@@ -529,7 +529,7 @@ EXAMPLES
529
529
  $ qfg info my.config.name --exclude-evaluations # skip 24h stats
530
530
  ```
531
531
 
532
- _See code: [src/commands/info.ts](https://github.com/quonfig/cli/blob/v0.0.23/src/commands/info.ts)_
532
+ _See code: [src/commands/info.ts](https://github.com/quonfig/cli/blob/v0.0.24/src/commands/info.ts)_
533
533
 
534
534
  ## `qfg init [DIRECTORY]`
535
535
 
@@ -569,7 +569,7 @@ EXAMPLES
569
569
  $ qfg init --dry-run
570
570
  ```
571
571
 
572
- _See code: [src/commands/init.ts](https://github.com/quonfig/cli/blob/v0.0.23/src/commands/init.ts)_
572
+ _See code: [src/commands/init.ts](https://github.com/quonfig/cli/blob/v0.0.24/src/commands/init.ts)_
573
573
 
574
574
  ## `qfg interactive`
575
575
 
@@ -599,7 +599,7 @@ EXAMPLES
599
599
  $ qfg
600
600
  ```
601
601
 
602
- _See code: [src/commands/interactive.ts](https://github.com/quonfig/cli/blob/v0.0.23/src/commands/interactive.ts)_
602
+ _See code: [src/commands/interactive.ts](https://github.com/quonfig/cli/blob/v0.0.24/src/commands/interactive.ts)_
603
603
 
604
604
  ## `qfg list`
605
605
 
@@ -639,7 +639,7 @@ EXAMPLES
639
639
  $ qfg list --feature-flags
640
640
  ```
641
641
 
642
- _See code: [src/commands/list.ts](https://github.com/quonfig/cli/blob/v0.0.23/src/commands/list.ts)_
642
+ _See code: [src/commands/list.ts](https://github.com/quonfig/cli/blob/v0.0.24/src/commands/list.ts)_
643
643
 
644
644
  ## `qfg log-level NAME`
645
645
 
@@ -704,7 +704,7 @@ EXAMPLES
704
704
  $ qfg log-level log-level.my-app --target=Chatty --value=INFO --environment=production
705
705
  ```
706
706
 
707
- _See code: [src/commands/log-level.ts](https://github.com/quonfig/cli/blob/v0.0.23/src/commands/log-level.ts)_
707
+ _See code: [src/commands/log-level.ts](https://github.com/quonfig/cli/blob/v0.0.24/src/commands/log-level.ts)_
708
708
 
709
709
  ## `qfg login`
710
710
 
@@ -729,7 +729,7 @@ EXAMPLES
729
729
  $ qfg login --profile myprofile
730
730
  ```
731
731
 
732
- _See code: [src/commands/login.ts](https://github.com/quonfig/cli/blob/v0.0.23/src/commands/login.ts)_
732
+ _See code: [src/commands/login.ts](https://github.com/quonfig/cli/blob/v0.0.24/src/commands/login.ts)_
733
733
 
734
734
  ## `qfg logout`
735
735
 
@@ -752,7 +752,7 @@ EXAMPLES
752
752
  $ qfg logout
753
753
  ```
754
754
 
755
- _See code: [src/commands/logout.ts](https://github.com/quonfig/cli/blob/v0.0.23/src/commands/logout.ts)_
755
+ _See code: [src/commands/logout.ts](https://github.com/quonfig/cli/blob/v0.0.24/src/commands/logout.ts)_
756
756
 
757
757
  ## `qfg mcp`
758
758
 
@@ -785,7 +785,7 @@ EXAMPLES
785
785
  $ qfg mcp --url http://local-app.quonfig-staging.com:3003/api/v1/mcp
786
786
  ```
787
787
 
788
- _See code: [src/commands/mcp.ts](https://github.com/quonfig/cli/blob/v0.0.23/src/commands/mcp.ts)_
788
+ _See code: [src/commands/mcp.ts](https://github.com/quonfig/cli/blob/v0.0.24/src/commands/mcp.ts)_
789
789
 
790
790
  ## `qfg migrate`
791
791
 
@@ -839,7 +839,7 @@ EXAMPLES
839
839
  $ qfg migrate --from launch --api-key $LAUNCH_API_KEY --dir ./quonfig-config --staging
840
840
  ```
841
841
 
842
- _See code: [src/commands/migrate.ts](https://github.com/quonfig/cli/blob/v0.0.23/src/commands/migrate.ts)_
842
+ _See code: [src/commands/migrate.ts](https://github.com/quonfig/cli/blob/v0.0.24/src/commands/migrate.ts)_
843
843
 
844
844
  ## `qfg migrate doctor`
845
845
 
@@ -877,7 +877,7 @@ EXAMPLES
877
877
  $ qfg migrate doctor --json
878
878
  ```
879
879
 
880
- _See code: [src/commands/migrate/doctor.ts](https://github.com/quonfig/cli/blob/v0.0.23/src/commands/migrate/doctor.ts)_
880
+ _See code: [src/commands/migrate/doctor.ts](https://github.com/quonfig/cli/blob/v0.0.24/src/commands/migrate/doctor.ts)_
881
881
 
882
882
  ## `qfg migrate my-code`
883
883
 
@@ -909,7 +909,7 @@ EXAMPLES
909
909
  $ qfg migrate my-code --dry-run
910
910
  ```
911
911
 
912
- _See code: [src/commands/migrate/my-code.ts](https://github.com/quonfig/cli/blob/v0.0.23/src/commands/migrate/my-code.ts)_
912
+ _See code: [src/commands/migrate/my-code.ts](https://github.com/quonfig/cli/blob/v0.0.24/src/commands/migrate/my-code.ts)_
913
913
 
914
914
  ## `qfg migrate status`
915
915
 
@@ -940,7 +940,7 @@ EXAMPLES
940
940
  $ qfg migrate status --json
941
941
  ```
942
942
 
943
- _See code: [src/commands/migrate/status.ts](https://github.com/quonfig/cli/blob/v0.0.23/src/commands/migrate/status.ts)_
943
+ _See code: [src/commands/migrate/status.ts](https://github.com/quonfig/cli/blob/v0.0.24/src/commands/migrate/status.ts)_
944
944
 
945
945
  ## `qfg override [NAME] [VALUE]`
946
946
 
@@ -995,7 +995,7 @@ EXAMPLES
995
995
  $ qfg override my.flag true --env=staging
996
996
  ```
997
997
 
998
- _See code: [src/commands/override.ts](https://github.com/quonfig/cli/blob/v0.0.23/src/commands/override.ts)_
998
+ _See code: [src/commands/override.ts](https://github.com/quonfig/cli/blob/v0.0.24/src/commands/override.ts)_
999
999
 
1000
1000
  ## `qfg pull`
1001
1001
 
@@ -1039,7 +1039,7 @@ EXAMPLES
1039
1039
  $ qfg pull # uses QUONFIG_DIR env var
1040
1040
  ```
1041
1041
 
1042
- _See code: [src/commands/pull.ts](https://github.com/quonfig/cli/blob/v0.0.23/src/commands/pull.ts)_
1042
+ _See code: [src/commands/pull.ts](https://github.com/quonfig/cli/blob/v0.0.24/src/commands/pull.ts)_
1043
1043
 
1044
1044
  ## `qfg push`
1045
1045
 
@@ -1087,7 +1087,7 @@ EXAMPLES
1087
1087
  $ qfg push --dir ./our-config --yes
1088
1088
  ```
1089
1089
 
1090
- _See code: [src/commands/push.ts](https://github.com/quonfig/cli/blob/v0.0.23/src/commands/push.ts)_
1090
+ _See code: [src/commands/push.ts](https://github.com/quonfig/cli/blob/v0.0.24/src/commands/push.ts)_
1091
1091
 
1092
1092
  ## `qfg schema NAME`
1093
1093
 
@@ -1125,7 +1125,7 @@ EXAMPLES
1125
1125
  $ qfg schema my-schema --set-json-schema=@schemas/my-schema.json --protected
1126
1126
  ```
1127
1127
 
1128
- _See code: [src/commands/schema.ts](https://github.com/quonfig/cli/blob/v0.0.23/src/commands/schema.ts)_
1128
+ _See code: [src/commands/schema.ts](https://github.com/quonfig/cli/blob/v0.0.24/src/commands/schema.ts)_
1129
1129
 
1130
1130
  ## `qfg sdk-key`
1131
1131
 
@@ -1152,7 +1152,7 @@ EXAMPLES
1152
1152
  $ qfg sdk-key revoke <key-id>
1153
1153
  ```
1154
1154
 
1155
- _See code: [src/commands/sdk-key.ts](https://github.com/quonfig/cli/blob/v0.0.23/src/commands/sdk-key.ts)_
1155
+ _See code: [src/commands/sdk-key.ts](https://github.com/quonfig/cli/blob/v0.0.24/src/commands/sdk-key.ts)_
1156
1156
 
1157
1157
  ## `qfg sdk-key create`
1158
1158
 
@@ -1184,7 +1184,7 @@ EXAMPLES
1184
1184
  $ qfg sdk-key create --environment staging --type browser
1185
1185
  ```
1186
1186
 
1187
- _See code: [src/commands/sdk-key/create.ts](https://github.com/quonfig/cli/blob/v0.0.23/src/commands/sdk-key/create.ts)_
1187
+ _See code: [src/commands/sdk-key/create.ts](https://github.com/quonfig/cli/blob/v0.0.24/src/commands/sdk-key/create.ts)_
1188
1188
 
1189
1189
  ## `qfg sdk-key list`
1190
1190
 
@@ -1213,7 +1213,7 @@ EXAMPLES
1213
1213
  $ qfg sdk-key list --environment production
1214
1214
  ```
1215
1215
 
1216
- _See code: [src/commands/sdk-key/list.ts](https://github.com/quonfig/cli/blob/v0.0.23/src/commands/sdk-key/list.ts)_
1216
+ _See code: [src/commands/sdk-key/list.ts](https://github.com/quonfig/cli/blob/v0.0.24/src/commands/sdk-key/list.ts)_
1217
1217
 
1218
1218
  ## `qfg sdk-key revoke KEYID`
1219
1219
 
@@ -1240,7 +1240,7 @@ EXAMPLES
1240
1240
  $ qfg sdk-key revoke a1b2c3d4-e5f6-7890-abcd-ef1234567890
1241
1241
  ```
1242
1242
 
1243
- _See code: [src/commands/sdk-key/revoke.ts](https://github.com/quonfig/cli/blob/v0.0.23/src/commands/sdk-key/revoke.ts)_
1243
+ _See code: [src/commands/sdk-key/revoke.ts](https://github.com/quonfig/cli/blob/v0.0.24/src/commands/sdk-key/revoke.ts)_
1244
1244
 
1245
1245
  ## `qfg set-default [NAME]`
1246
1246
 
@@ -1311,7 +1311,7 @@ EXAMPLES
1311
1311
  $ qfg pull && qfg config-schema
1312
1312
  ```
1313
1313
 
1314
- _See code: [src/commands/set-default.ts](https://github.com/quonfig/cli/blob/v0.0.23/src/commands/set-default.ts)_
1314
+ _See code: [src/commands/set-default.ts](https://github.com/quonfig/cli/blob/v0.0.24/src/commands/set-default.ts)_
1315
1315
 
1316
1316
  ## `qfg set-rollout [NAME]`
1317
1317
 
@@ -1388,7 +1388,7 @@ EXAMPLES
1388
1388
  $ qfg set-rollout my.variant.flag --environment production --weights "a:33,b:33,c:34" --hash-by user.id
1389
1389
  ```
1390
1390
 
1391
- _See code: [src/commands/set-rollout.ts](https://github.com/quonfig/cli/blob/v0.0.23/src/commands/set-rollout.ts)_
1391
+ _See code: [src/commands/set-rollout.ts](https://github.com/quonfig/cli/blob/v0.0.24/src/commands/set-rollout.ts)_
1392
1392
 
1393
1393
  ## `qfg sync`
1394
1394
 
@@ -1420,7 +1420,7 @@ EXAMPLES
1420
1420
  $ qfg sync --watch --dir ./our-config --interval 30
1421
1421
  ```
1422
1422
 
1423
- _See code: [src/commands/sync.ts](https://github.com/quonfig/cli/blob/v0.0.23/src/commands/sync.ts)_
1423
+ _See code: [src/commands/sync.ts](https://github.com/quonfig/cli/blob/v0.0.24/src/commands/sync.ts)_
1424
1424
 
1425
1425
  ## `qfg toggle [NAME]`
1426
1426
 
@@ -1522,7 +1522,7 @@ EXAMPLES
1522
1522
  $ qfg verify --json
1523
1523
  ```
1524
1524
 
1525
- _See code: [src/commands/verify.ts](https://github.com/quonfig/cli/blob/v0.0.23/src/commands/verify.ts)_
1525
+ _See code: [src/commands/verify.ts](https://github.com/quonfig/cli/blob/v0.0.24/src/commands/verify.ts)_
1526
1526
 
1527
1527
  ## `qfg whoami`
1528
1528
 
@@ -1545,7 +1545,7 @@ EXAMPLES
1545
1545
  $ qfg whoami
1546
1546
  ```
1547
1547
 
1548
- _See code: [src/commands/whoami.ts](https://github.com/quonfig/cli/blob/v0.0.23/src/commands/whoami.ts)_
1548
+ _See code: [src/commands/whoami.ts](https://github.com/quonfig/cli/blob/v0.0.24/src/commands/whoami.ts)_
1549
1549
 
1550
1550
  ## `qfg workspace`
1551
1551
 
@@ -1568,7 +1568,7 @@ EXAMPLES
1568
1568
  $ qfg workspace
1569
1569
  ```
1570
1570
 
1571
- _See code: [src/commands/workspace.ts](https://github.com/quonfig/cli/blob/v0.0.23/src/commands/workspace.ts)_
1571
+ _See code: [src/commands/workspace.ts](https://github.com/quonfig/cli/blob/v0.0.24/src/commands/workspace.ts)_
1572
1572
 
1573
1573
  ## `qfg workspace bootstrap`
1574
1574
 
@@ -1601,7 +1601,7 @@ EXAMPLES
1601
1601
  $ qfg workspace bootstrap --dir ./our-config --skip-validate
1602
1602
  ```
1603
1603
 
1604
- _See code: [src/commands/workspace/bootstrap.ts](https://github.com/quonfig/cli/blob/v0.0.23/src/commands/workspace/bootstrap.ts)_
1604
+ _See code: [src/commands/workspace/bootstrap.ts](https://github.com/quonfig/cli/blob/v0.0.24/src/commands/workspace/bootstrap.ts)_
1605
1605
 
1606
1606
  ## `qfg workspace create SLUG`
1607
1607
 
@@ -1635,7 +1635,7 @@ EXAMPLES
1635
1635
  $ qfg workspace create my-team --org 11111111-1111-1111-1111-111111111111
1636
1636
  ```
1637
1637
 
1638
- _See code: [src/commands/workspace/create.ts](https://github.com/quonfig/cli/blob/v0.0.23/src/commands/workspace/create.ts)_
1638
+ _See code: [src/commands/workspace/create.ts](https://github.com/quonfig/cli/blob/v0.0.24/src/commands/workspace/create.ts)_
1639
1639
 
1640
1640
  ## `qfg workspace switch [SLUG]`
1641
1641
 
@@ -1663,7 +1663,7 @@ EXAMPLES
1663
1663
  $ qfg workspace switch my-workspace-slug
1664
1664
  ```
1665
1665
 
1666
- _See code: [src/commands/workspace/switch.ts](https://github.com/quonfig/cli/blob/v0.0.23/src/commands/workspace/switch.ts)_
1666
+ _See code: [src/commands/workspace/switch.ts](https://github.com/quonfig/cli/blob/v0.0.24/src/commands/workspace/switch.ts)_
1667
1667
  <!-- commandsstop -->
1668
1668
 
1669
1669
  ## Local Development
@@ -122,9 +122,7 @@ For more complex rules (regex, multi-criterion, exact match) edit the JSON:
122
122
  // Merge semantics: if an existing rule has the same exact targeting
123
123
  // criterion (same propertyName, same valueToMatch set), replace its value.
124
124
  // Otherwise insert the new rule before the catch-all so it takes priority.
125
- const existingRules = environment
126
- ? findEnvRules(current, environment)
127
- : current.default?.rules ?? [];
125
+ const existingRules = environment ? findEnvRules(current, environment) : (current.default?.rules ?? []);
128
126
  const mergedRules = mergeTargetingRule(existingRules, newRule);
129
127
  const logLevelPatch = environment
130
128
  ? { environments: replaceEnvRules(current.environments ?? [], environment, mergedRules) }
@@ -3,6 +3,7 @@ import { select } from '@inquirer/prompts';
3
3
  import { BaseCommand } from '../index.js';
4
4
  import { decodeJWT, pollForToken, requestDeviceCode } from '../util/oauth-client.js';
5
5
  import { getApiUrl } from '../util/domain-urls.js';
6
+ import { openBrowser } from '../util/open-browser.js';
6
7
  import { getAuthConfigFilePath, getTokenFilePath, loadAuthConfig, saveAuthConfig, saveTokens, } from '../util/token-storage.js';
7
8
  export default class Login extends BaseCommand {
8
9
  static description = 'Log in to Quonfig via WorkOS device authorization';
@@ -27,9 +28,37 @@ export default class Login extends BaseCommand {
27
28
  this.log(`\nTo authenticate, visit:\n`);
28
29
  this.log(` ${deviceAuth.verification_uri_complete}\n`);
29
30
  this.log(`Or go to ${deviceAuth.verification_uri} and enter code: ${deviceAuth.user_code}\n`);
31
+ // Listen for Enter to open the URL in the user's browser.
32
+ // Runs concurrently with token polling so the user can also authenticate
33
+ // by pasting the URL manually without having to press Enter first.
34
+ const stdinIsTTY = Boolean(process.stdin.isTTY);
35
+ let browserOpened = false;
36
+ const onEnter = (chunk) => {
37
+ if (browserOpened)
38
+ return;
39
+ if (!chunk.toString().match(/[\r\n]/))
40
+ return;
41
+ browserOpened = true;
42
+ openBrowser(deviceAuth.verification_uri_complete);
43
+ this.log('Opening browser...');
44
+ };
45
+ if (stdinIsTTY) {
46
+ this.log('Press Enter to open the URL in your browser, or open it manually.');
47
+ process.stdin.on('data', onEnter);
48
+ process.stdin.resume();
49
+ }
30
50
  this.log('Waiting for authentication...');
31
51
  // Step 3: Poll for token
32
- const tokenResponse = await pollForToken(deviceAuth.device_code, deviceAuth.interval, deviceAuth.expires_in, this.isVerbose);
52
+ let tokenResponse;
53
+ try {
54
+ tokenResponse = await pollForToken(deviceAuth.device_code, deviceAuth.interval, deviceAuth.expires_in, this.isVerbose);
55
+ }
56
+ finally {
57
+ if (stdinIsTTY) {
58
+ process.stdin.off('data', onEnter);
59
+ process.stdin.pause();
60
+ }
61
+ }
33
62
  const user = tokenResponse.user;
34
63
  const userEmail = user.email;
35
64
  // Decode JWT to inspect claims
@@ -13,10 +13,7 @@ export default class WorkspaceSwitch extends BaseCommand {
13
13
  }),
14
14
  };
15
15
  static description = 'Switch to a different workspace';
16
- static examples = [
17
- '<%= config.bin %> <%= command.id %>',
18
- '<%= config.bin %> <%= command.id %> my-workspace-slug',
19
- ];
16
+ static examples = ['<%= config.bin %> <%= command.id %>', '<%= config.bin %> <%= command.id %> my-workspace-slug'];
20
17
  async run() {
21
18
  const { args } = await this.parse(WorkspaceSwitch);
22
19
  // Get a valid token — refresh silently if needed, error if not logged in
@@ -3,7 +3,7 @@ import * as path from 'node:path';
3
3
  import { removeQfFromGitignore, writeImportState } from './import-state.js';
4
4
  import { writeMigrationReport } from './migration-report.js';
5
5
  import { detectDuplicateKeys } from './sources/launch/translate.js';
6
- import { cloneAndStackPush } from '../util/clone-and-stack-push.js';
6
+ import { cloneAndStackPush, } from '../util/clone-and-stack-push.js';
7
7
  const writeQuonfigFiles = (dir, changes, source) => {
8
8
  const livePaths = new Map();
9
9
  for (const change of changes) {
@@ -59,8 +59,7 @@ const mergeEnvironmentsIntoQuonfigJson = (dir, sourceEnvs) => {
59
59
  }
60
60
  const existingEnvs = Array.isArray(existing.environments) ? existing.environments : [];
61
61
  const merged = [...new Set([...existingEnvs, ...sourceEnvs])].sort();
62
- if (existingEnvs.length === merged.length &&
63
- existingEnvs.every((e, i) => e === merged[i])) {
62
+ if (existingEnvs.length === merged.length && existingEnvs.every((e, i) => e === merged[i])) {
64
63
  return;
65
64
  }
66
65
  fs.writeFileSync(quonfigPath, JSON.stringify({ ...existing, environments: merged }, null, 2) + '\n', 'utf8');
@@ -80,9 +79,7 @@ export const pushMigrationToCloud = async (opts) => {
80
79
  droppedOverrides = opts.source.getDroppedOverrides?.() ?? null;
81
80
  skippedConfigs = opts.source.getSkippedConfigs?.() ?? null;
82
81
  duplicateResolutions =
83
- resolutionEntries.length > 0
84
- ? { entries: resolutionEntries, total: resolutionEntries.length }
85
- : null;
82
+ resolutionEntries.length > 0 ? { entries: resolutionEntries, total: resolutionEntries.length } : null;
86
83
  const reportData = {
87
84
  ...opts.reportData,
88
85
  ...(droppedOverrides ? { droppedOverrides } : {}),
@@ -2,27 +2,25 @@
2
2
  // Vendored verbatim from launch-migrator/src/zod-to-json-schema.ts to keep a
3
3
  // drop-in sync path. Do not reformat — edits here should be mirrored against
4
4
  // launch-migrator so the two converters stay behaviorally identical.
5
- import * as ts from "typescript";
6
- const JSON_SCHEMA_DRAFT_2020_12 = "https://json-schema.org/draft/2020-12/schema";
5
+ import * as ts from 'typescript';
6
+ const JSON_SCHEMA_DRAFT_2020_12 = 'https://json-schema.org/draft/2020-12/schema';
7
7
  function unwrapExpression(node) {
8
8
  let current = node;
9
- while (ts.isParenthesizedExpression(current) ||
10
- ts.isAsExpression(current) ||
11
- ts.isTypeAssertionExpression(current)) {
9
+ while (ts.isParenthesizedExpression(current) || ts.isAsExpression(current) || ts.isTypeAssertionExpression(current)) {
12
10
  current = current.expression;
13
11
  }
14
12
  return current;
15
13
  }
16
14
  function parseRegexLiteral(text) {
17
- if (!text.startsWith("/")) {
15
+ if (!text.startsWith('/')) {
18
16
  throw new Error(`Expected regex literal, got: ${text}`);
19
17
  }
20
18
  let lastSlash = -1;
21
19
  for (let index = text.length - 1; index > 0; index -= 1) {
22
- if (text[index] !== "/")
20
+ if (text[index] !== '/')
23
21
  continue;
24
22
  let backslashCount = 0;
25
- for (let i = index - 1; i >= 0 && text[i] === "\\"; i -= 1) {
23
+ for (let i = index - 1; i >= 0 && text[i] === '\\'; i -= 1) {
26
24
  backslashCount += 1;
27
25
  }
28
26
  if (backslashCount % 2 === 0) {
@@ -39,7 +37,7 @@ function parseRegexLiteral(text) {
39
37
  };
40
38
  }
41
39
  function isPlainObject(value) {
42
- return typeof value === "object" && value !== null && !Array.isArray(value);
40
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
43
41
  }
44
42
  function evaluateLiteralExpression(node) {
45
43
  const current = unwrapExpression(node);
@@ -57,13 +55,13 @@ function evaluateLiteralExpression(node) {
57
55
  return null;
58
56
  if (ts.isPrefixUnaryExpression(current) && current.operator === ts.SyntaxKind.MinusToken) {
59
57
  const operand = evaluateLiteralExpression(current.operand);
60
- if (typeof operand === "number")
58
+ if (typeof operand === 'number')
61
59
  return -operand;
62
60
  }
63
61
  if (ts.isArrayLiteralExpression(current)) {
64
62
  return current.elements.map((element) => {
65
63
  if (ts.isSpreadElement(element)) {
66
- throw new Error("Spread elements are not supported in literal arrays");
64
+ throw new Error('Spread elements are not supported in literal arrays');
67
65
  }
68
66
  return evaluateLiteralExpression(element);
69
67
  });
@@ -72,10 +70,10 @@ function evaluateLiteralExpression(node) {
72
70
  const result = {};
73
71
  for (const property of current.properties) {
74
72
  if (ts.isSpreadAssignment(property) || ts.isShorthandPropertyAssignment(property)) {
75
- throw new Error("Spread and shorthand properties are not supported in literal objects");
73
+ throw new Error('Spread and shorthand properties are not supported in literal objects');
76
74
  }
77
75
  if (!ts.isPropertyAssignment(property)) {
78
- throw new Error("Unsupported object literal property");
76
+ throw new Error('Unsupported object literal property');
79
77
  }
80
78
  const key = getPropertyName(property.name);
81
79
  result[key] = evaluateLiteralExpression(property.initializer);
@@ -88,14 +86,14 @@ function evaluateLiteralExpression(node) {
88
86
  if (ts.isBinaryExpression(current) && current.operatorToken.kind === ts.SyntaxKind.PlusToken) {
89
87
  const left = evaluateLiteralExpression(current.left);
90
88
  const right = evaluateLiteralExpression(current.right);
91
- if (typeof left === "string" || typeof right === "string") {
89
+ if (typeof left === 'string' || typeof right === 'string') {
92
90
  return String(left) + String(right);
93
91
  }
94
- if (typeof left === "number" && typeof right === "number") {
92
+ if (typeof left === 'number' && typeof right === 'number') {
95
93
  return left + right;
96
94
  }
97
95
  }
98
- if (ts.isIdentifier(current) && current.text === "undefined") {
96
+ if (ts.isIdentifier(current) && current.text === 'undefined') {
99
97
  return undefined;
100
98
  }
101
99
  throw new Error(`Unsupported literal expression: ${current.getText()}`);
@@ -108,38 +106,38 @@ function getPropertyName(name) {
108
106
  }
109
107
  function schemaKind(schema) {
110
108
  if (schema === false)
111
- return "never";
109
+ return 'never';
112
110
  if (!isPlainObject(schema))
113
- return "unknown";
111
+ return 'unknown';
114
112
  if (Array.isArray(schema.anyOf))
115
- return "union";
113
+ return 'union';
116
114
  if (schema.const !== undefined)
117
- return "const";
115
+ return 'const';
118
116
  if (Array.isArray(schema.enum))
119
- return "enum";
117
+ return 'enum';
120
118
  const type = schema.type;
121
- if (typeof type === "string")
119
+ if (typeof type === 'string')
122
120
  return type;
123
- return "unknown";
121
+ return 'unknown';
124
122
  }
125
123
  function ensureObjectSchema(schema) {
126
- if (schema === false || !isPlainObject(schema) || schema.type !== "object") {
127
- throw new Error("Expected object schema");
124
+ if (schema === false || !isPlainObject(schema) || schema.type !== 'object') {
125
+ throw new Error('Expected object schema');
128
126
  }
129
127
  return schema;
130
128
  }
131
129
  function makeNullable(schema) {
132
130
  if (!isPlainObject(schema)) {
133
- return { anyOf: [schema, { type: "null" }] };
131
+ return { anyOf: [schema, { type: 'null' }] };
134
132
  }
135
133
  const existing = schema.type;
136
134
  if (Array.isArray(existing)) {
137
- return existing.includes("null") ? schema : { ...schema, type: [...existing, "null"] };
135
+ return existing.includes('null') ? schema : { ...schema, type: [...existing, 'null'] };
138
136
  }
139
- if (typeof existing === "string") {
140
- return { ...schema, type: [existing, "null"] };
137
+ if (typeof existing === 'string') {
138
+ return { ...schema, type: [existing, 'null'] };
141
139
  }
142
- return { anyOf: [schema, { type: "null" }] };
140
+ return { anyOf: [schema, { type: 'null' }] };
143
141
  }
144
142
  function withAnnotation(schema, key, value) {
145
143
  if (schema === false) {
@@ -153,24 +151,24 @@ function applyNumericConstraint(schema, kind, method, value) {
153
151
  if (schema === false)
154
152
  return schema;
155
153
  const next = { ...schema };
156
- if (kind === "number" || kind === "integer") {
157
- if (method === "min" || method === "length")
154
+ if (kind === 'number' || kind === 'integer') {
155
+ if (method === 'min' || method === 'length')
158
156
  next.minimum = value;
159
- if (method === "max" || method === "length")
157
+ if (method === 'max' || method === 'length')
160
158
  next.maximum = value;
161
159
  return next;
162
160
  }
163
- if (kind === "string") {
164
- if (method === "min" || method === "length")
161
+ if (kind === 'string') {
162
+ if (method === 'min' || method === 'length')
165
163
  next.minLength = value;
166
- if (method === "max" || method === "length")
164
+ if (method === 'max' || method === 'length')
167
165
  next.maxLength = value;
168
166
  return next;
169
167
  }
170
- if (kind === "array") {
171
- if (method === "min" || method === "length")
168
+ if (kind === 'array') {
169
+ if (method === 'min' || method === 'length')
172
170
  next.minItems = value;
173
- if (method === "max" || method === "length")
171
+ if (method === 'max' || method === 'length')
174
172
  next.maxItems = value;
175
173
  return next;
176
174
  }
@@ -179,35 +177,35 @@ function applyNumericConstraint(schema, kind, method, value) {
179
177
  function applyMethod(receiver, method, args, ctx) {
180
178
  const kind = schemaKind(receiver.schema);
181
179
  switch (method) {
182
- case "optional":
180
+ case 'optional':
183
181
  return { ...receiver, optional: true };
184
- case "nullable":
182
+ case 'nullable':
185
183
  return { schema: makeNullable(receiver.schema), optional: receiver.optional };
186
- case "default": {
184
+ case 'default': {
187
185
  if (args.length === 0)
188
- throw new Error("default() requires an argument");
186
+ throw new Error('default() requires an argument');
189
187
  return {
190
- schema: withAnnotation(receiver.schema, "default", evaluateLiteralExpression(args[0])),
188
+ schema: withAnnotation(receiver.schema, 'default', evaluateLiteralExpression(args[0])),
191
189
  optional: true,
192
190
  };
193
191
  }
194
- case "describe": {
192
+ case 'describe': {
195
193
  if (args.length === 0)
196
- throw new Error("describe() requires an argument");
194
+ throw new Error('describe() requires an argument');
197
195
  return {
198
- schema: withAnnotation(receiver.schema, "description", evaluateLiteralExpression(args[0])),
196
+ schema: withAnnotation(receiver.schema, 'description', evaluateLiteralExpression(args[0])),
199
197
  optional: receiver.optional,
200
198
  };
201
199
  }
202
- case "meta": {
200
+ case 'meta': {
203
201
  if (args.length === 0)
204
- throw new Error("meta() requires an argument");
202
+ throw new Error('meta() requires an argument');
205
203
  const meta = evaluateLiteralExpression(args[0]);
206
204
  if (!isPlainObject(meta)) {
207
- throw new Error("meta() expects an object literal");
205
+ throw new Error('meta() expects an object literal');
208
206
  }
209
207
  let schema = receiver.schema;
210
- const supportedKeys = new Set(["description", "title"]);
208
+ const supportedKeys = new Set(['description', 'title']);
211
209
  for (const [key, value] of Object.entries(meta)) {
212
210
  if (supportedKeys.has(key)) {
213
211
  schema = withAnnotation(schema, key, value);
@@ -218,99 +216,99 @@ function applyMethod(receiver, method, args, ctx) {
218
216
  }
219
217
  return { schema, optional: receiver.optional };
220
218
  }
221
- case "url":
222
- if (kind !== "string") {
223
- throw new Error("url() can only be applied to string schemas");
219
+ case 'url':
220
+ if (kind !== 'string') {
221
+ throw new Error('url() can only be applied to string schemas');
224
222
  }
225
223
  return {
226
- schema: withAnnotation(receiver.schema, "format", "uri"),
224
+ schema: withAnnotation(receiver.schema, 'format', 'uri'),
227
225
  optional: receiver.optional,
228
226
  };
229
- case "email":
230
- if (kind !== "string") {
231
- throw new Error("email() can only be applied to string schemas");
227
+ case 'email':
228
+ if (kind !== 'string') {
229
+ throw new Error('email() can only be applied to string schemas');
232
230
  }
233
231
  return {
234
- schema: withAnnotation(receiver.schema, "format", "email"),
232
+ schema: withAnnotation(receiver.schema, 'format', 'email'),
235
233
  optional: receiver.optional,
236
234
  };
237
- case "uuid":
238
- if (kind !== "string") {
239
- throw new Error("uuid() can only be applied to string schemas");
235
+ case 'uuid':
236
+ if (kind !== 'string') {
237
+ throw new Error('uuid() can only be applied to string schemas');
240
238
  }
241
239
  return {
242
- schema: withAnnotation(receiver.schema, "format", "uuid"),
240
+ schema: withAnnotation(receiver.schema, 'format', 'uuid'),
243
241
  optional: receiver.optional,
244
242
  };
245
- case "datetime":
246
- if (kind !== "string") {
247
- throw new Error("datetime() can only be applied to string schemas");
243
+ case 'datetime':
244
+ if (kind !== 'string') {
245
+ throw new Error('datetime() can only be applied to string schemas');
248
246
  }
249
247
  return {
250
- schema: withAnnotation(receiver.schema, "format", "date-time"),
248
+ schema: withAnnotation(receiver.schema, 'format', 'date-time'),
251
249
  optional: receiver.optional,
252
250
  };
253
- case "regex": {
254
- if (kind !== "string") {
255
- throw new Error("regex() can only be applied to string schemas");
251
+ case 'regex': {
252
+ if (kind !== 'string') {
253
+ throw new Error('regex() can only be applied to string schemas');
256
254
  }
257
255
  if (args.length === 0)
258
- throw new Error("regex() requires an argument");
256
+ throw new Error('regex() requires an argument');
259
257
  const regex = evaluateLiteralExpression(args[0]);
260
- if (!isPlainObject(regex) || typeof regex.pattern !== "string") {
261
- throw new Error("regex() expects a regex literal");
258
+ if (!isPlainObject(regex) || typeof regex.pattern !== 'string') {
259
+ throw new Error('regex() expects a regex literal');
262
260
  }
263
- if (typeof regex.flags === "string" && regex.flags.length > 0) {
261
+ if (typeof regex.flags === 'string' && regex.flags.length > 0) {
264
262
  ctx.warnings.push(`Ignored regex flags "${regex.flags}"`);
265
263
  }
266
264
  return {
267
- schema: withAnnotation(receiver.schema, "pattern", regex.pattern),
265
+ schema: withAnnotation(receiver.schema, 'pattern', regex.pattern),
268
266
  optional: receiver.optional,
269
267
  };
270
268
  }
271
- case "min":
269
+ case 'min':
272
270
  if (args.length === 0)
273
- throw new Error("min() requires an argument");
271
+ throw new Error('min() requires an argument');
274
272
  return {
275
- schema: applyNumericConstraint(receiver.schema, kind, "min", Number(evaluateLiteralExpression(args[0]))),
273
+ schema: applyNumericConstraint(receiver.schema, kind, 'min', Number(evaluateLiteralExpression(args[0]))),
276
274
  optional: receiver.optional,
277
275
  };
278
- case "max":
276
+ case 'max':
279
277
  if (args.length === 0)
280
- throw new Error("max() requires an argument");
278
+ throw new Error('max() requires an argument');
281
279
  return {
282
- schema: applyNumericConstraint(receiver.schema, kind, "max", Number(evaluateLiteralExpression(args[0]))),
280
+ schema: applyNumericConstraint(receiver.schema, kind, 'max', Number(evaluateLiteralExpression(args[0]))),
283
281
  optional: receiver.optional,
284
282
  };
285
- case "length":
283
+ case 'length':
286
284
  if (args.length === 0)
287
- throw new Error("length() requires an argument");
285
+ throw new Error('length() requires an argument');
288
286
  return {
289
- schema: applyNumericConstraint(receiver.schema, kind, "length", Number(evaluateLiteralExpression(args[0]))),
287
+ schema: applyNumericConstraint(receiver.schema, kind, 'length', Number(evaluateLiteralExpression(args[0]))),
290
288
  optional: receiver.optional,
291
289
  };
292
- case "int": {
293
- if (kind !== "number" && kind !== "integer") {
294
- throw new Error("int() can only be applied to number schemas");
290
+ case 'int': {
291
+ if (kind !== 'number' && kind !== 'integer') {
292
+ throw new Error('int() can only be applied to number schemas');
295
293
  }
296
- const next = withAnnotation(receiver.schema, "type", "integer");
294
+ const next = withAnnotation(receiver.schema, 'type', 'integer');
297
295
  return { schema: next, optional: receiver.optional };
298
296
  }
299
- case "positive": {
300
- if (kind !== "number" && kind !== "integer") {
301
- throw new Error("positive() can only be applied to number schemas");
297
+ case 'positive': {
298
+ if (kind !== 'number' && kind !== 'integer') {
299
+ throw new Error('positive() can only be applied to number schemas');
302
300
  }
303
- const next = withAnnotation(receiver.schema, "exclusiveMinimum", 0);
301
+ const next = withAnnotation(receiver.schema, 'exclusiveMinimum', 0);
304
302
  return { schema: next, optional: receiver.optional };
305
303
  }
306
- case "strict": {
304
+ case 'strict': {
307
305
  const objectSchema = ensureObjectSchema(receiver.schema);
308
306
  return {
309
307
  schema: { ...objectSchema, additionalProperties: false },
310
308
  optional: receiver.optional,
311
309
  };
312
310
  }
313
- case "partial": {
311
+ case 'partial': {
314
312
  // .partial() makes all declared properties optional. For records
315
313
  // (open-shape objects) there's nothing to do.
316
314
  if (!isPlainObject(receiver.schema)) {
@@ -319,17 +317,17 @@ function applyMethod(receiver, method, args, ctx) {
319
317
  const { required: _required, ...rest } = receiver.schema;
320
318
  return { schema: rest, optional: receiver.optional };
321
319
  }
322
- case "array":
320
+ case 'array':
323
321
  return {
324
322
  schema: {
325
- type: "array",
323
+ type: 'array',
326
324
  items: receiver.schema,
327
325
  },
328
326
  optional: receiver.optional,
329
327
  };
330
- case "or": {
328
+ case 'or': {
331
329
  if (args.length === 0)
332
- throw new Error("or() requires an argument");
330
+ throw new Error('or() requires an argument');
333
331
  const other = convertExpression(args[0], ctx);
334
332
  return {
335
333
  schema: { anyOf: [receiver.schema, other.schema] },
@@ -345,7 +343,7 @@ function convertObjectShape(objectLiteral, ctx) {
345
343
  const required = [];
346
344
  for (const property of objectLiteral.properties) {
347
345
  if (!ts.isPropertyAssignment(property)) {
348
- throw new Error("Only property assignments are supported in z.object()");
346
+ throw new Error('Only property assignments are supported in z.object()');
349
347
  }
350
348
  const key = getPropertyName(property.name);
351
349
  const child = convertExpression(property.initializer, ctx);
@@ -355,20 +353,20 @@ function convertObjectShape(objectLiteral, ctx) {
355
353
  }
356
354
  }
357
355
  return {
358
- type: "object",
356
+ type: 'object',
359
357
  properties,
360
358
  required,
361
359
  };
362
360
  }
363
361
  function convertBaseZodCall(method, args, ctx) {
364
362
  switch (method) {
365
- case "string":
366
- return { schema: { type: "string" }, optional: false };
367
- case "number":
368
- return { schema: { type: "number" }, optional: false };
369
- case "boolean":
370
- return { schema: { type: "boolean" }, optional: false };
371
- case "enum": {
363
+ case 'string':
364
+ return { schema: { type: 'string' }, optional: false };
365
+ case 'number':
366
+ return { schema: { type: 'number' }, optional: false };
367
+ case 'boolean':
368
+ return { schema: { type: 'boolean' }, optional: false };
369
+ case 'enum': {
372
370
  // Canonical form: z.enum(["a", "b"]). Also tolerate the hand-written
373
371
  // typo z.enum("a", "b") by treating variadic args as an implicit array.
374
372
  let elements;
@@ -379,46 +377,46 @@ function convertBaseZodCall(method, args, ctx) {
379
377
  elements = args;
380
378
  }
381
379
  else {
382
- throw new Error("enum() expects at least one argument");
380
+ throw new Error('enum() expects at least one argument');
383
381
  }
384
382
  const values = elements.map((element) => evaluateLiteralExpression(element));
385
383
  return { schema: { enum: values }, optional: false };
386
384
  }
387
- case "literal": {
385
+ case 'literal': {
388
386
  if (args.length !== 1)
389
- throw new Error("literal() expects a single argument");
387
+ throw new Error('literal() expects a single argument');
390
388
  return { schema: { const: evaluateLiteralExpression(args[0]) }, optional: false };
391
389
  }
392
- case "never":
390
+ case 'never':
393
391
  return { schema: false, optional: false };
394
- case "array": {
392
+ case 'array': {
395
393
  if (args.length !== 1) {
396
- throw new Error("array() expects a single argument");
394
+ throw new Error('array() expects a single argument');
397
395
  }
398
396
  const item = convertExpression(args[0], ctx);
399
397
  return {
400
398
  schema: {
401
- type: "array",
399
+ type: 'array',
402
400
  items: item.schema,
403
401
  },
404
402
  optional: false,
405
403
  };
406
404
  }
407
- case "object": {
405
+ case 'object': {
408
406
  if (args.length === 0) {
409
- return { schema: { type: "object" }, optional: false };
407
+ return { schema: { type: 'object' }, optional: false };
410
408
  }
411
409
  if (args.length !== 1 || !ts.isObjectLiteralExpression(args[0])) {
412
- throw new Error("object() expects a single object literal argument");
410
+ throw new Error('object() expects a single object literal argument');
413
411
  }
414
412
  return {
415
413
  schema: convertObjectShape(args[0], ctx),
416
414
  optional: false,
417
415
  };
418
416
  }
419
- case "record": {
417
+ case 'record': {
420
418
  if (args.length === 0 || args.length > 2) {
421
- throw new Error("record() expects one or two arguments");
419
+ throw new Error('record() expects one or two arguments');
422
420
  }
423
421
  let keySchema;
424
422
  let valueIndex = 0;
@@ -428,7 +426,7 @@ function convertBaseZodCall(method, args, ctx) {
428
426
  }
429
427
  const valueSchema = convertExpression(args[valueIndex], ctx);
430
428
  const schema = {
431
- type: "object",
429
+ type: 'object',
432
430
  additionalProperties: valueSchema.schema,
433
431
  };
434
432
  if (keySchema && isPlainObject(keySchema.schema) && Array.isArray(keySchema.schema.enum)) {
@@ -436,19 +434,19 @@ function convertBaseZodCall(method, args, ctx) {
436
434
  }
437
435
  return { schema, optional: false };
438
436
  }
439
- case "union": {
437
+ case 'union': {
440
438
  if (args.length !== 1 || !ts.isArrayLiteralExpression(args[0])) {
441
- throw new Error("union() expects a single array literal argument");
439
+ throw new Error('union() expects a single array literal argument');
442
440
  }
443
441
  const options = args[0].elements.map((element) => convertExpression(element, ctx).schema);
444
442
  return { schema: { anyOf: options }, optional: false };
445
443
  }
446
- case "tuple": {
444
+ case 'tuple': {
447
445
  if (args.length !== 1 || !ts.isArrayLiteralExpression(args[0])) {
448
- throw new Error("tuple() expects a single array literal argument");
446
+ throw new Error('tuple() expects a single array literal argument');
449
447
  }
450
448
  const items = args[0].elements.map((element) => convertExpression(element, ctx).schema);
451
- const schema = { type: "array", minItems: items.length, maxItems: items.length };
449
+ const schema = { type: 'array', minItems: items.length, maxItems: items.length };
452
450
  if (items.length > 0) {
453
451
  schema.prefixItems = items;
454
452
  schema.items = false;
@@ -469,7 +467,7 @@ function convertCallExpression(call, ctx) {
469
467
  const receiverExpr = unwrapExpression(callee.expression);
470
468
  if (ts.isIdentifier(receiverExpr)) {
471
469
  const base = receiverExpr.text;
472
- if (base === "z") {
470
+ if (base === 'z') {
473
471
  return convertBaseZodCall(method, call.arguments, ctx);
474
472
  }
475
473
  }
@@ -477,8 +475,8 @@ function convertCallExpression(call, ctx) {
477
475
  return applyMethod(receiver, method, call.arguments, ctx);
478
476
  }
479
477
  if (ts.isIdentifier(callee)) {
480
- if (callee.text === "z") {
481
- throw new Error("Unexpected bare z() call");
478
+ if (callee.text === 'z') {
479
+ throw new Error('Unexpected bare z() call');
482
480
  }
483
481
  }
484
482
  throw new Error(`Unsupported call expression: ${call.getText()}`);
@@ -493,22 +491,22 @@ function convertExpression(node, ctx) {
493
491
  // schema, so the intent is always the method call).
494
492
  if (ts.isPropertyAccessExpression(expr)) {
495
493
  const method = expr.name.text;
496
- if (method === "nullable" || method === "optional") {
494
+ if (method === 'nullable' || method === 'optional') {
497
495
  const receiver = convertExpression(expr.expression, ctx);
498
496
  return applyMethod(receiver, method, ts.factory.createNodeArray([]), ctx);
499
497
  }
500
498
  }
501
- if (ts.isIdentifier(expr) && expr.text === "undefined") {
499
+ if (ts.isIdentifier(expr) && expr.text === 'undefined') {
502
500
  return { schema: false, optional: true };
503
501
  }
504
502
  throw new Error(`Unsupported Zod expression: ${expr.getText()}`);
505
503
  }
506
504
  export function zodToJsonSchema(source) {
507
- const sourceFile = ts.createSourceFile("schema.ts", source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
505
+ const sourceFile = ts.createSourceFile('schema.ts', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
508
506
  const ctx = { warnings: [] };
509
507
  const expressionStatement = sourceFile.statements.find(ts.isExpressionStatement);
510
508
  if (!expressionStatement) {
511
- throw new Error("Expected a Zod expression");
509
+ throw new Error('Expected a Zod expression');
512
510
  }
513
511
  const result = convertExpression(expressionStatement.expression, ctx);
514
512
  if (result.schema !== false && isPlainObject(result.schema)) {
@@ -1,5 +1,5 @@
1
1
  import { fetchAllChangeHistory, fetchEnvironments } from './launch/api.js';
2
- import { InvalidSourceConfigError, getOutputPath, isLegacyLogLevel, slugify, transformConfig } from './launch/translate.js';
2
+ import { InvalidSourceConfigError, getOutputPath, isLegacyLogLevel, slugify, transformConfig, } from './launch/translate.js';
3
3
  const SOURCE_NAME = 'launch';
4
4
  const state = { apiKey: null, droppedOverrides: new Map(), envIdMap: null, skippedConfigs: [] };
5
5
  class MissingAuthError extends Error {
@@ -0,0 +1 @@
1
+ export declare const openBrowser: (url: string) => void;
@@ -0,0 +1,27 @@
1
+ import { spawn } from 'node:child_process';
2
+ export const openBrowser = (url) => {
3
+ let command;
4
+ let args;
5
+ if (process.platform === 'darwin') {
6
+ command = 'open';
7
+ args = [url];
8
+ }
9
+ else if (process.platform === 'win32') {
10
+ command = 'cmd';
11
+ args = ['/c', 'start', '""', url.replace(/&/g, '^&')];
12
+ }
13
+ else {
14
+ command = 'xdg-open';
15
+ args = [url];
16
+ }
17
+ try {
18
+ const child = spawn(command, args, { detached: true, stdio: 'ignore' });
19
+ child.on('error', () => {
20
+ // Swallow — user can still copy the URL manually.
21
+ });
22
+ child.unref();
23
+ }
24
+ catch {
25
+ // Swallow — user can still copy the URL manually.
26
+ }
27
+ };
@@ -46,7 +46,9 @@ export async function resolveWorkspaceUuid(command, flagOverride) {
46
46
  const match = entries.find((w) => w.workspaceSlug === override);
47
47
  if (!match) {
48
48
  const available = entries.map((w) => w.workspaceSlug).join(', ') || '(none)';
49
- command.error(`No workspace with slug "${override}" is accessible with this API key. Available: ${available}.`, { exit: 1 });
49
+ command.error(`No workspace with slug "${override}" is accessible with this API key. Available: ${available}.`, {
50
+ exit: 1,
51
+ });
50
52
  }
51
53
  command.verboseLog('ApiKey auth', { source: 'slug', slug: override, workspaceId: match.workspaceId });
52
54
  return match.workspaceId;
@@ -111,7 +113,9 @@ async function recoverAuthConfigFromTokens(command) {
111
113
  command.error(`Tokens found but failed to fetch workspaces: ${error instanceof Error ? error.message : String(error)}. Run \`qfg login\` to repopulate.`, { exit: 401 });
112
114
  }
113
115
  if (!res.ok) {
114
- command.error(`Tokens found but workspace list returned HTTP ${res.status}. Run \`qfg login\` to repopulate.`, { exit: 401 });
116
+ command.error(`Tokens found but workspace list returned HTTP ${res.status}. Run \`qfg login\` to repopulate.`, {
117
+ exit: 401,
118
+ });
115
119
  }
116
120
  const body = (await res.json());
117
121
  const entries = (body.json ?? body);
@@ -2983,5 +2983,5 @@
2983
2983
  ]
2984
2984
  }
2985
2985
  },
2986
- "version": "0.0.23"
2986
+ "version": "0.0.24"
2987
2987
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "packageManager": "yarn@4.11.0+sha512.4e54aeace9141df2f0177c266b05ec50dc044638157dae128c471ba65994ac802122d7ab35bcd9e81641228b7dcf24867d28e750e0bcae8a05277d600008ad54",
3
3
  "name": "@quonfig/cli",
4
- "version": "0.0.23",
4
+ "version": "0.0.24",
5
5
  "bugs": {
6
6
  "url": "https://github.com/quonfig/cli/issues"
7
7
  },