@quonfig/cli 0.0.16 → 0.0.18

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.
@@ -0,0 +1,521 @@
1
+ /* eslint-disable @typescript-eslint/no-unused-vars, perfectionist/sort-interfaces, unicorn/explicit-length-check, unicorn/no-lonely-if, unicorn/no-array-callback-reference */
2
+ // Vendored verbatim from launch-migrator/src/zod-to-json-schema.ts to keep a
3
+ // drop-in sync path. Do not reformat — edits here should be mirrored against
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";
7
+ function unwrapExpression(node) {
8
+ let current = node;
9
+ while (ts.isParenthesizedExpression(current) ||
10
+ ts.isAsExpression(current) ||
11
+ ts.isTypeAssertionExpression(current)) {
12
+ current = current.expression;
13
+ }
14
+ return current;
15
+ }
16
+ function parseRegexLiteral(text) {
17
+ if (!text.startsWith("/")) {
18
+ throw new Error(`Expected regex literal, got: ${text}`);
19
+ }
20
+ let lastSlash = -1;
21
+ for (let index = text.length - 1; index > 0; index -= 1) {
22
+ if (text[index] !== "/")
23
+ continue;
24
+ let backslashCount = 0;
25
+ for (let i = index - 1; i >= 0 && text[i] === "\\"; i -= 1) {
26
+ backslashCount += 1;
27
+ }
28
+ if (backslashCount % 2 === 0) {
29
+ lastSlash = index;
30
+ break;
31
+ }
32
+ }
33
+ if (lastSlash === -1) {
34
+ throw new Error(`Could not parse regex literal: ${text}`);
35
+ }
36
+ return {
37
+ pattern: text.slice(1, lastSlash),
38
+ flags: text.slice(lastSlash + 1),
39
+ };
40
+ }
41
+ function isPlainObject(value) {
42
+ return typeof value === "object" && value !== null && !Array.isArray(value);
43
+ }
44
+ function evaluateLiteralExpression(node) {
45
+ const current = unwrapExpression(node);
46
+ if (ts.isStringLiteral(current) || ts.isNoSubstitutionTemplateLiteral(current)) {
47
+ return current.text;
48
+ }
49
+ if (ts.isNumericLiteral(current)) {
50
+ return Number(current.text);
51
+ }
52
+ if (current.kind === ts.SyntaxKind.TrueKeyword)
53
+ return true;
54
+ if (current.kind === ts.SyntaxKind.FalseKeyword)
55
+ return false;
56
+ if (current.kind === ts.SyntaxKind.NullKeyword)
57
+ return null;
58
+ if (ts.isPrefixUnaryExpression(current) && current.operator === ts.SyntaxKind.MinusToken) {
59
+ const operand = evaluateLiteralExpression(current.operand);
60
+ if (typeof operand === "number")
61
+ return -operand;
62
+ }
63
+ if (ts.isArrayLiteralExpression(current)) {
64
+ return current.elements.map((element) => {
65
+ if (ts.isSpreadElement(element)) {
66
+ throw new Error("Spread elements are not supported in literal arrays");
67
+ }
68
+ return evaluateLiteralExpression(element);
69
+ });
70
+ }
71
+ if (ts.isObjectLiteralExpression(current)) {
72
+ const result = {};
73
+ for (const property of current.properties) {
74
+ if (ts.isSpreadAssignment(property) || ts.isShorthandPropertyAssignment(property)) {
75
+ throw new Error("Spread and shorthand properties are not supported in literal objects");
76
+ }
77
+ if (!ts.isPropertyAssignment(property)) {
78
+ throw new Error("Unsupported object literal property");
79
+ }
80
+ const key = getPropertyName(property.name);
81
+ result[key] = evaluateLiteralExpression(property.initializer);
82
+ }
83
+ return result;
84
+ }
85
+ if (ts.isRegularExpressionLiteral(current)) {
86
+ return parseRegexLiteral(current.getText());
87
+ }
88
+ if (ts.isBinaryExpression(current) && current.operatorToken.kind === ts.SyntaxKind.PlusToken) {
89
+ const left = evaluateLiteralExpression(current.left);
90
+ const right = evaluateLiteralExpression(current.right);
91
+ if (typeof left === "string" || typeof right === "string") {
92
+ return String(left) + String(right);
93
+ }
94
+ if (typeof left === "number" && typeof right === "number") {
95
+ return left + right;
96
+ }
97
+ }
98
+ if (ts.isIdentifier(current) && current.text === "undefined") {
99
+ return undefined;
100
+ }
101
+ throw new Error(`Unsupported literal expression: ${current.getText()}`);
102
+ }
103
+ function getPropertyName(name) {
104
+ if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name)) {
105
+ return name.text;
106
+ }
107
+ throw new Error(`Unsupported property name: ${name.getText()}`);
108
+ }
109
+ function schemaKind(schema) {
110
+ if (schema === false)
111
+ return "never";
112
+ if (!isPlainObject(schema))
113
+ return "unknown";
114
+ if (Array.isArray(schema.anyOf))
115
+ return "union";
116
+ if (schema.const !== undefined)
117
+ return "const";
118
+ if (Array.isArray(schema.enum))
119
+ return "enum";
120
+ const type = schema.type;
121
+ if (typeof type === "string")
122
+ return type;
123
+ return "unknown";
124
+ }
125
+ function ensureObjectSchema(schema) {
126
+ if (schema === false || !isPlainObject(schema) || schema.type !== "object") {
127
+ throw new Error("Expected object schema");
128
+ }
129
+ return schema;
130
+ }
131
+ function makeNullable(schema) {
132
+ if (!isPlainObject(schema)) {
133
+ return { anyOf: [schema, { type: "null" }] };
134
+ }
135
+ const existing = schema.type;
136
+ if (Array.isArray(existing)) {
137
+ return existing.includes("null") ? schema : { ...schema, type: [...existing, "null"] };
138
+ }
139
+ if (typeof existing === "string") {
140
+ return { ...schema, type: [existing, "null"] };
141
+ }
142
+ return { anyOf: [schema, { type: "null" }] };
143
+ }
144
+ function withAnnotation(schema, key, value) {
145
+ if (schema === false) {
146
+ return schema;
147
+ }
148
+ const next = { ...schema };
149
+ next[key] = value;
150
+ return next;
151
+ }
152
+ function applyNumericConstraint(schema, kind, method, value) {
153
+ if (schema === false)
154
+ return schema;
155
+ const next = { ...schema };
156
+ if (kind === "number" || kind === "integer") {
157
+ if (method === "min" || method === "length")
158
+ next.minimum = value;
159
+ if (method === "max" || method === "length")
160
+ next.maximum = value;
161
+ return next;
162
+ }
163
+ if (kind === "string") {
164
+ if (method === "min" || method === "length")
165
+ next.minLength = value;
166
+ if (method === "max" || method === "length")
167
+ next.maxLength = value;
168
+ return next;
169
+ }
170
+ if (kind === "array") {
171
+ if (method === "min" || method === "length")
172
+ next.minItems = value;
173
+ if (method === "max" || method === "length")
174
+ next.maxItems = value;
175
+ return next;
176
+ }
177
+ throw new Error(`Cannot apply ${method}() to ${kind} schema`);
178
+ }
179
+ function applyMethod(receiver, method, args, ctx) {
180
+ const kind = schemaKind(receiver.schema);
181
+ switch (method) {
182
+ case "optional":
183
+ return { ...receiver, optional: true };
184
+ case "nullable":
185
+ return { schema: makeNullable(receiver.schema), optional: receiver.optional };
186
+ case "default": {
187
+ if (args.length === 0)
188
+ throw new Error("default() requires an argument");
189
+ return {
190
+ schema: withAnnotation(receiver.schema, "default", evaluateLiteralExpression(args[0])),
191
+ optional: true,
192
+ };
193
+ }
194
+ case "describe": {
195
+ if (args.length === 0)
196
+ throw new Error("describe() requires an argument");
197
+ return {
198
+ schema: withAnnotation(receiver.schema, "description", evaluateLiteralExpression(args[0])),
199
+ optional: receiver.optional,
200
+ };
201
+ }
202
+ case "meta": {
203
+ if (args.length === 0)
204
+ throw new Error("meta() requires an argument");
205
+ const meta = evaluateLiteralExpression(args[0]);
206
+ if (!isPlainObject(meta)) {
207
+ throw new Error("meta() expects an object literal");
208
+ }
209
+ let schema = receiver.schema;
210
+ const supportedKeys = new Set(["description", "title"]);
211
+ for (const [key, value] of Object.entries(meta)) {
212
+ if (supportedKeys.has(key)) {
213
+ schema = withAnnotation(schema, key, value);
214
+ }
215
+ else {
216
+ ctx.warnings.push(`Ignored unsupported meta field "${key}"`);
217
+ }
218
+ }
219
+ return { schema, optional: receiver.optional };
220
+ }
221
+ case "url":
222
+ if (kind !== "string") {
223
+ throw new Error("url() can only be applied to string schemas");
224
+ }
225
+ return {
226
+ schema: withAnnotation(receiver.schema, "format", "uri"),
227
+ optional: receiver.optional,
228
+ };
229
+ case "email":
230
+ if (kind !== "string") {
231
+ throw new Error("email() can only be applied to string schemas");
232
+ }
233
+ return {
234
+ schema: withAnnotation(receiver.schema, "format", "email"),
235
+ optional: receiver.optional,
236
+ };
237
+ case "uuid":
238
+ if (kind !== "string") {
239
+ throw new Error("uuid() can only be applied to string schemas");
240
+ }
241
+ return {
242
+ schema: withAnnotation(receiver.schema, "format", "uuid"),
243
+ optional: receiver.optional,
244
+ };
245
+ case "datetime":
246
+ if (kind !== "string") {
247
+ throw new Error("datetime() can only be applied to string schemas");
248
+ }
249
+ return {
250
+ schema: withAnnotation(receiver.schema, "format", "date-time"),
251
+ optional: receiver.optional,
252
+ };
253
+ case "regex": {
254
+ if (kind !== "string") {
255
+ throw new Error("regex() can only be applied to string schemas");
256
+ }
257
+ if (args.length === 0)
258
+ throw new Error("regex() requires an argument");
259
+ const regex = evaluateLiteralExpression(args[0]);
260
+ if (!isPlainObject(regex) || typeof regex.pattern !== "string") {
261
+ throw new Error("regex() expects a regex literal");
262
+ }
263
+ if (typeof regex.flags === "string" && regex.flags.length > 0) {
264
+ ctx.warnings.push(`Ignored regex flags "${regex.flags}"`);
265
+ }
266
+ return {
267
+ schema: withAnnotation(receiver.schema, "pattern", regex.pattern),
268
+ optional: receiver.optional,
269
+ };
270
+ }
271
+ case "min":
272
+ if (args.length === 0)
273
+ throw new Error("min() requires an argument");
274
+ return {
275
+ schema: applyNumericConstraint(receiver.schema, kind, "min", Number(evaluateLiteralExpression(args[0]))),
276
+ optional: receiver.optional,
277
+ };
278
+ case "max":
279
+ if (args.length === 0)
280
+ throw new Error("max() requires an argument");
281
+ return {
282
+ schema: applyNumericConstraint(receiver.schema, kind, "max", Number(evaluateLiteralExpression(args[0]))),
283
+ optional: receiver.optional,
284
+ };
285
+ case "length":
286
+ if (args.length === 0)
287
+ throw new Error("length() requires an argument");
288
+ return {
289
+ schema: applyNumericConstraint(receiver.schema, kind, "length", Number(evaluateLiteralExpression(args[0]))),
290
+ optional: receiver.optional,
291
+ };
292
+ case "int": {
293
+ if (kind !== "number" && kind !== "integer") {
294
+ throw new Error("int() can only be applied to number schemas");
295
+ }
296
+ const next = withAnnotation(receiver.schema, "type", "integer");
297
+ return { schema: next, optional: receiver.optional };
298
+ }
299
+ case "positive": {
300
+ if (kind !== "number" && kind !== "integer") {
301
+ throw new Error("positive() can only be applied to number schemas");
302
+ }
303
+ const next = withAnnotation(receiver.schema, "exclusiveMinimum", 0);
304
+ return { schema: next, optional: receiver.optional };
305
+ }
306
+ case "strict": {
307
+ const objectSchema = ensureObjectSchema(receiver.schema);
308
+ return {
309
+ schema: { ...objectSchema, additionalProperties: false },
310
+ optional: receiver.optional,
311
+ };
312
+ }
313
+ case "partial": {
314
+ // .partial() makes all declared properties optional. For records
315
+ // (open-shape objects) there's nothing to do.
316
+ if (!isPlainObject(receiver.schema)) {
317
+ return { ...receiver };
318
+ }
319
+ const { required: _required, ...rest } = receiver.schema;
320
+ return { schema: rest, optional: receiver.optional };
321
+ }
322
+ case "array":
323
+ return {
324
+ schema: {
325
+ type: "array",
326
+ items: receiver.schema,
327
+ },
328
+ optional: receiver.optional,
329
+ };
330
+ case "or": {
331
+ if (args.length === 0)
332
+ throw new Error("or() requires an argument");
333
+ const other = convertExpression(args[0], ctx);
334
+ return {
335
+ schema: { anyOf: [receiver.schema, other.schema] },
336
+ optional: receiver.optional || other.optional,
337
+ };
338
+ }
339
+ default:
340
+ throw new Error(`Unsupported Zod method: ${method}`);
341
+ }
342
+ }
343
+ function convertObjectShape(objectLiteral, ctx) {
344
+ const properties = {};
345
+ const required = [];
346
+ for (const property of objectLiteral.properties) {
347
+ if (!ts.isPropertyAssignment(property)) {
348
+ throw new Error("Only property assignments are supported in z.object()");
349
+ }
350
+ const key = getPropertyName(property.name);
351
+ const child = convertExpression(property.initializer, ctx);
352
+ properties[key] = child.schema;
353
+ if (!child.optional) {
354
+ required.push(key);
355
+ }
356
+ }
357
+ return {
358
+ type: "object",
359
+ properties,
360
+ required,
361
+ };
362
+ }
363
+ function convertBaseZodCall(method, args, ctx) {
364
+ 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": {
372
+ // Canonical form: z.enum(["a", "b"]). Also tolerate the hand-written
373
+ // typo z.enum("a", "b") by treating variadic args as an implicit array.
374
+ let elements;
375
+ if (args.length === 1 && ts.isArrayLiteralExpression(args[0])) {
376
+ elements = args[0].elements;
377
+ }
378
+ else if (args.length >= 1) {
379
+ elements = args;
380
+ }
381
+ else {
382
+ throw new Error("enum() expects at least one argument");
383
+ }
384
+ const values = elements.map((element) => evaluateLiteralExpression(element));
385
+ return { schema: { enum: values }, optional: false };
386
+ }
387
+ case "literal": {
388
+ if (args.length !== 1)
389
+ throw new Error("literal() expects a single argument");
390
+ return { schema: { const: evaluateLiteralExpression(args[0]) }, optional: false };
391
+ }
392
+ case "never":
393
+ return { schema: false, optional: false };
394
+ case "array": {
395
+ if (args.length !== 1) {
396
+ throw new Error("array() expects a single argument");
397
+ }
398
+ const item = convertExpression(args[0], ctx);
399
+ return {
400
+ schema: {
401
+ type: "array",
402
+ items: item.schema,
403
+ },
404
+ optional: false,
405
+ };
406
+ }
407
+ case "object": {
408
+ if (args.length === 0) {
409
+ return { schema: { type: "object" }, optional: false };
410
+ }
411
+ if (args.length !== 1 || !ts.isObjectLiteralExpression(args[0])) {
412
+ throw new Error("object() expects a single object literal argument");
413
+ }
414
+ return {
415
+ schema: convertObjectShape(args[0], ctx),
416
+ optional: false,
417
+ };
418
+ }
419
+ case "record": {
420
+ if (args.length === 0 || args.length > 2) {
421
+ throw new Error("record() expects one or two arguments");
422
+ }
423
+ let keySchema;
424
+ let valueIndex = 0;
425
+ if (args.length === 2) {
426
+ keySchema = convertExpression(args[0], ctx);
427
+ valueIndex = 1;
428
+ }
429
+ const valueSchema = convertExpression(args[valueIndex], ctx);
430
+ const schema = {
431
+ type: "object",
432
+ additionalProperties: valueSchema.schema,
433
+ };
434
+ if (keySchema && isPlainObject(keySchema.schema) && Array.isArray(keySchema.schema.enum)) {
435
+ schema.propertyNames = { enum: keySchema.schema.enum };
436
+ }
437
+ return { schema, optional: false };
438
+ }
439
+ case "union": {
440
+ if (args.length !== 1 || !ts.isArrayLiteralExpression(args[0])) {
441
+ throw new Error("union() expects a single array literal argument");
442
+ }
443
+ const options = args[0].elements.map((element) => convertExpression(element, ctx).schema);
444
+ return { schema: { anyOf: options }, optional: false };
445
+ }
446
+ case "tuple": {
447
+ if (args.length !== 1 || !ts.isArrayLiteralExpression(args[0])) {
448
+ throw new Error("tuple() expects a single array literal argument");
449
+ }
450
+ const items = args[0].elements.map((element) => convertExpression(element, ctx).schema);
451
+ const schema = { type: "array", minItems: items.length, maxItems: items.length };
452
+ if (items.length > 0) {
453
+ schema.prefixItems = items;
454
+ schema.items = false;
455
+ }
456
+ else {
457
+ schema.items = false;
458
+ }
459
+ return { schema, optional: false };
460
+ }
461
+ default:
462
+ throw new Error(`Unsupported base Zod call: ${method}`);
463
+ }
464
+ }
465
+ function convertCallExpression(call, ctx) {
466
+ const callee = unwrapExpression(call.expression);
467
+ if (ts.isPropertyAccessExpression(callee)) {
468
+ const method = callee.name.text;
469
+ const receiverExpr = unwrapExpression(callee.expression);
470
+ if (ts.isIdentifier(receiverExpr)) {
471
+ const base = receiverExpr.text;
472
+ if (base === "z") {
473
+ return convertBaseZodCall(method, call.arguments, ctx);
474
+ }
475
+ }
476
+ const receiver = convertExpression(receiverExpr, ctx);
477
+ return applyMethod(receiver, method, call.arguments, ctx);
478
+ }
479
+ if (ts.isIdentifier(callee)) {
480
+ if (callee.text === "z") {
481
+ throw new Error("Unexpected bare z() call");
482
+ }
483
+ }
484
+ throw new Error(`Unsupported call expression: ${call.getText()}`);
485
+ }
486
+ function convertExpression(node, ctx) {
487
+ const expr = unwrapExpression(node);
488
+ if (ts.isCallExpression(expr)) {
489
+ return convertCallExpression(expr, ctx);
490
+ }
491
+ // Tolerate hand-written Zod that omits the parens on .nullable/.optional
492
+ // (a common user typo — property access returns the method function, not a
493
+ // schema, so the intent is always the method call).
494
+ if (ts.isPropertyAccessExpression(expr)) {
495
+ const method = expr.name.text;
496
+ if (method === "nullable" || method === "optional") {
497
+ const receiver = convertExpression(expr.expression, ctx);
498
+ return applyMethod(receiver, method, ts.factory.createNodeArray([]), ctx);
499
+ }
500
+ }
501
+ if (ts.isIdentifier(expr) && expr.text === "undefined") {
502
+ return { schema: false, optional: true };
503
+ }
504
+ throw new Error(`Unsupported Zod expression: ${expr.getText()}`);
505
+ }
506
+ export function zodToJsonSchema(source) {
507
+ const sourceFile = ts.createSourceFile("schema.ts", source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
508
+ const ctx = { warnings: [] };
509
+ const expressionStatement = sourceFile.statements.find(ts.isExpressionStatement);
510
+ if (!expressionStatement) {
511
+ throw new Error("Expected a Zod expression");
512
+ }
513
+ const result = convertExpression(expressionStatement.expression, ctx);
514
+ if (result.schema !== false && isPlainObject(result.schema)) {
515
+ result.schema.$schema = JSON_SCHEMA_DRAFT_2020_12;
516
+ }
517
+ return {
518
+ schema: result.schema,
519
+ warnings: ctx.warnings,
520
+ };
521
+ }
@@ -1,7 +1,7 @@
1
1
  import { fetchAllChangeHistory, fetchEnvironments } from './launch/api.js';
2
- import { 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
- const state = { apiKey: null, envIdMap: null };
4
+ const state = { apiKey: null, droppedOverrides: new Map(), envIdMap: null, skippedConfigs: [] };
5
5
  class MissingAuthError extends Error {
6
6
  constructor(operation) {
7
7
  super(`launch source ${operation} requires validateAuth(apiKey) to be called first (no API key configured).`);
@@ -42,23 +42,66 @@ function translateImpl(change) {
42
42
  return [];
43
43
  if (typeof raw.type === 'string' && isLegacyLogLevel(raw.type))
44
44
  return [];
45
- if (raw.deleted)
46
- return [];
45
+ if (raw.deleted) {
46
+ return [{ deleted: true, path: getOutputPath(raw.type, raw.key) }];
47
+ }
47
48
  if (!raw.newConfig)
48
49
  return [];
49
50
  const envIdMap = state.envIdMap ?? {};
50
- const transformed = transformConfig(raw.newConfig, envIdMap);
51
- const path = getOutputPath(raw.type, raw.key);
52
- return [{ contents: JSON.stringify(transformed, null, 2), path }];
51
+ const outputPath = getOutputPath(raw.type, raw.key);
52
+ let transformed;
53
+ try {
54
+ transformed = transformConfig(raw.newConfig, envIdMap, (envId) => {
55
+ let perFlag = state.droppedOverrides.get(envId);
56
+ if (!perFlag) {
57
+ perFlag = new Map();
58
+ state.droppedOverrides.set(envId, perFlag);
59
+ }
60
+ perFlag.set(outputPath, (perFlag.get(outputPath) ?? 0) + 1);
61
+ });
62
+ }
63
+ catch (error) {
64
+ if (error instanceof InvalidSourceConfigError) {
65
+ state.skippedConfigs.push({ key: raw.key, reason: error.message });
66
+ return [];
67
+ }
68
+ throw error;
69
+ }
70
+ return [{ contents: JSON.stringify(transformed, null, 2), path: outputPath }];
71
+ }
72
+ function getDroppedOverridesImpl() {
73
+ if (state.droppedOverrides.size === 0)
74
+ return null;
75
+ const byEnv = {};
76
+ let total = 0;
77
+ for (const envId of [...state.droppedOverrides.keys()].sort()) {
78
+ const perFlag = state.droppedOverrides.get(envId);
79
+ const record = {};
80
+ for (const [flagPath, count] of [...perFlag.entries()].sort(([a], [b]) => a.localeCompare(b))) {
81
+ record[flagPath] = count;
82
+ total += count;
83
+ }
84
+ byEnv[envId] = record;
85
+ }
86
+ return { byEnv, total };
87
+ }
88
+ function getSkippedConfigsImpl() {
89
+ if (state.skippedConfigs.length === 0)
90
+ return null;
91
+ return { entries: [...state.skippedConfigs], total: state.skippedConfigs.length };
53
92
  }
54
93
  async function validateAuthImpl(apiKey) {
55
94
  await fetchEnvironments(apiKey);
56
95
  state.apiKey = apiKey;
96
+ state.droppedOverrides = new Map();
97
+ state.skippedConfigs = [];
57
98
  }
58
99
  export const launchSource = {
59
100
  fetchChanges(sinceEpochMs) {
60
101
  return fetchChangesImpl(sinceEpochMs);
61
102
  },
103
+ getDroppedOverrides: getDroppedOverridesImpl,
104
+ getSkippedConfigs: getSkippedConfigsImpl,
62
105
  listEnvironments: listEnvironmentsImpl,
63
106
  name: SOURCE_NAME,
64
107
  translate: translateImpl,
@@ -67,4 +110,6 @@ export const launchSource = {
67
110
  export function __resetLaunchSourceForTests() {
68
111
  state.apiKey = null;
69
112
  state.envIdMap = null;
113
+ state.droppedOverrides = new Map();
114
+ state.skippedConfigs = [];
70
115
  }
@@ -29,7 +29,7 @@ const getClient = async (command, sdkKey, profile) => {
29
29
  // API-key (workspace-scoped bearer) path. getValidAccessToken short-circuits
30
30
  // when QUONFIG_API_KEY is set, so this never touches disk.
31
31
  try {
32
- jwt = await getValidAccessToken();
32
+ jwt = await getValidAccessToken(command.verboseLog);
33
33
  }
34
34
  catch (error) {
35
35
  command.error(error instanceof Error ? error.message : String(error), { exit: 1 });
@@ -81,11 +81,12 @@ const getClient = async (command, sdkKey, profile) => {
81
81
  }
82
82
  command.verboseLog('Checking token validity...');
83
83
  try {
84
- jwt = await getValidAccessToken();
84
+ jwt = await getValidAccessToken(command.verboseLog);
85
85
  command.verboseLog('Token valid (or refreshed)');
86
86
  }
87
- catch {
88
- command.error('Session expired. Please run `qfg login` to re-authenticate.', { exit: 401 });
87
+ catch (error) {
88
+ const detail = error instanceof Error ? error.message : String(error);
89
+ command.error(`Session expired. Please run \`qfg login\` to re-authenticate. (${detail})`, { exit: 401 });
89
90
  }
90
91
  // Resolve workspace: --workspace/QUONFIG_WORKSPACE (slug) → QUONFIG_PROFILE (profile name) → default
91
92
  const workspaceSlugOverride = profile ?? process.env.QUONFIG_WORKSPACE;
@@ -6,4 +6,6 @@
6
6
  * directly as a bearer token and no disk reads/writes or refresh calls
7
7
  * are made. This is the non-interactive (CI) path.
8
8
  */
9
- export declare function getValidAccessToken(): Promise<string>;
9
+ type Logger = (msg: string, data?: unknown) => void;
10
+ export declare function getValidAccessToken(log?: Logger): Promise<string>;
11
+ export {};