@quonfig/cli 0.0.49 → 0.0.51
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 +50 -48
- package/dist/commands/generate.js +21 -5
- package/dist/commands/info.d.ts +1 -0
- package/dist/commands/info.js +28 -1
- package/dist/commands/migrate.d.ts +1 -1
- package/dist/commands/migrate.js +16 -5
- package/dist/commands/pull.js +2 -2
- package/dist/init/templates.js +59 -12
- package/dist/migrate/local-write.js +11 -1
- package/dist/migrate/migration-report.d.ts +16 -5
- package/dist/migrate/migration-report.js +351 -20
- package/dist/migrate/push-to-cloud.js +6 -1
- package/dist/migrate/quonfig-target/report.d.ts +45 -4
- package/dist/migrate/quonfig-target/report.js +5 -2
- package/dist/migrate/quonfig-target/ruleset.js +8 -6
- package/dist/migrate/source.d.ts +27 -0
- package/dist/migrate/sources/flagsmith/api.d.ts +115 -0
- package/dist/migrate/sources/flagsmith/api.js +402 -0
- package/dist/migrate/sources/flagsmith/translate.d.ts +72 -0
- package/dist/migrate/sources/flagsmith/translate.js +657 -0
- package/dist/migrate/sources/flagsmith/types.d.ts +282 -0
- package/dist/migrate/sources/flagsmith/types.js +18 -0
- package/dist/migrate/sources/flagsmith.d.ts +62 -0
- package/dist/migrate/sources/flagsmith.js +380 -23
- package/dist/migrate/sources/launchdarkly/api.d.ts +22 -1
- package/dist/migrate/sources/launchdarkly/api.js +23 -1
- package/dist/migrate/sources/launchdarkly/translate.js +42 -2
- package/dist/migrate/sources/launchdarkly/types.d.ts +19 -0
- package/dist/migrate/sources/launchdarkly.js +75 -4
- package/dist/util/friction-log.d.ts +1 -0
- package/dist/util/friction-log.js +6 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/oclif.manifest.json +158 -160
- package/package.json +2 -2
|
@@ -12,7 +12,7 @@ import * as fs from 'node:fs';
|
|
|
12
12
|
import * as os from 'node:os';
|
|
13
13
|
import * as path from 'node:path';
|
|
14
14
|
import { ConversionReport } from '../quonfig-target/report.js';
|
|
15
|
-
import { fetchProjectEnvironments, fetchSegmentsForEnv, fetchSnapshot } from './launchdarkly/api.js';
|
|
15
|
+
import { fetchMembers, fetchProjectEnvironments, fetchProjectEnvironmentsDetailed, fetchSegmentsForEnv, fetchSnapshot, } from './launchdarkly/api.js';
|
|
16
16
|
import { auditEntryToLegacyChange, buildFlagAuditSpec, getCommitMetaForAuditEntry, probeRetentionHorizon, walkAuditLog, } from './launchdarkly/audit.js';
|
|
17
17
|
import { flagOutputPath, segmentOutputPath, translateFlag, translateSegment } from './launchdarkly/translate.js';
|
|
18
18
|
const SOURCE_NAME = 'launchdarkly';
|
|
@@ -39,7 +39,9 @@ function resolveProjectKey() {
|
|
|
39
39
|
const state = {
|
|
40
40
|
apiKey: null,
|
|
41
41
|
environments: [],
|
|
42
|
+
environmentSourceNames: [],
|
|
42
43
|
fullSummary: false,
|
|
44
|
+
maintainerMap: null,
|
|
43
45
|
projectKey: resolveProjectKey(),
|
|
44
46
|
report: new ConversionReport(),
|
|
45
47
|
retentionHorizon: null,
|
|
@@ -89,11 +91,15 @@ function slugifyEnvKey(key) {
|
|
|
89
91
|
}
|
|
90
92
|
async function validateAuthImpl(apiKey) {
|
|
91
93
|
// A cheap authenticated call — 401s on a bad token, 404s on a bad project.
|
|
92
|
-
|
|
94
|
+
// Use the detailed call so we can stash {key, name} for the report's env
|
|
95
|
+
// mapping table (qfg-1t7m) without a second round-trip.
|
|
96
|
+
const detailed = await fetchProjectEnvironmentsDetailed(apiKey, state.projectKey);
|
|
93
97
|
state.apiKey = apiKey;
|
|
94
98
|
state.report = new ConversionReport();
|
|
95
99
|
state.environments = [];
|
|
100
|
+
state.environmentSourceNames = detailed;
|
|
96
101
|
state.retentionHorizon = null;
|
|
102
|
+
state.maintainerMap = null;
|
|
97
103
|
// Plan §4.1.1: under --full-summary, probe the audit-log retention window
|
|
98
104
|
// here — the earliest authenticated hook — so the command can tell the user
|
|
99
105
|
// the real history horizon BEFORE the slow Phase-2 walk starts. The walk can
|
|
@@ -103,10 +109,33 @@ async function validateAuthImpl(apiKey) {
|
|
|
103
109
|
state.retentionHorizon = await probeRetentionHorizon(apiKey, { spec: buildFlagAuditSpec(state.projectKey) });
|
|
104
110
|
}
|
|
105
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* qfg-l8uz: best-effort `/members` lookup so MIGRATION_REPORT.md can render
|
|
114
|
+
* `maintainerId → email`. Project-scoped tokens or accounts that hide member
|
|
115
|
+
* data will 403 here — that's fine, the migration must not fail just because
|
|
116
|
+
* the rollup loses a label. Returns null on any error, the empty map when the
|
|
117
|
+
* endpoint returns no members, and a stripped map otherwise (members without
|
|
118
|
+
* an email are skipped — we have nothing useful to render for them).
|
|
119
|
+
*/
|
|
120
|
+
async function loadMaintainerMap(apiKey) {
|
|
121
|
+
try {
|
|
122
|
+
const members = await fetchMembers(apiKey);
|
|
123
|
+
const map = {};
|
|
124
|
+
for (const member of members) {
|
|
125
|
+
if (member.email)
|
|
126
|
+
map[member._id] = member.email;
|
|
127
|
+
}
|
|
128
|
+
return map;
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
106
134
|
async function listEnvironmentsImpl() {
|
|
107
135
|
const apiKey = requireApiKey('listEnvironments');
|
|
108
|
-
const
|
|
109
|
-
state.
|
|
136
|
+
const detailed = await fetchProjectEnvironmentsDetailed(apiKey, state.projectKey);
|
|
137
|
+
state.environmentSourceNames = detailed;
|
|
138
|
+
state.environments = detailed.map((e) => slugifyEnvKey(e.key));
|
|
110
139
|
return [...new Set(state.environments)];
|
|
111
140
|
}
|
|
112
141
|
/**
|
|
@@ -120,6 +149,11 @@ async function listEnvironmentsImpl() {
|
|
|
120
149
|
*/
|
|
121
150
|
async function* fetchChangesImpl() {
|
|
122
151
|
const apiKey = requireApiKey('fetchChanges');
|
|
152
|
+
// qfg-l8uz: load the account-wide member directory once so the report can
|
|
153
|
+
// render maintainerId → email pairs. Deliberately best-effort — a failed
|
|
154
|
+
// call (project-scoped token, hidden member data) just leaves the rollup
|
|
155
|
+
// showing opaque hex IDs.
|
|
156
|
+
state.maintainerMap = await loadMaintainerMap(apiKey);
|
|
123
157
|
if (state.fullSummary) {
|
|
124
158
|
yield* fetchAuditHistory(apiKey);
|
|
125
159
|
return;
|
|
@@ -243,6 +277,39 @@ function getSkippedConfigsImpl() {
|
|
|
243
277
|
export function getConversionReport() {
|
|
244
278
|
return state.report;
|
|
245
279
|
}
|
|
280
|
+
/**
|
|
281
|
+
* qfg-l8uz: maintainerId → email map produced by `/members` during
|
|
282
|
+
* validateAuth, or null when the call failed (project-scoped token, account
|
|
283
|
+
* hides members, etc.). The report renderer surfaces this as a sub-table on
|
|
284
|
+
* `## Identifier map` and decorates `dropped-maintainer` rollups so a reader
|
|
285
|
+
* sees real emails alongside the opaque hex IDs.
|
|
286
|
+
*/
|
|
287
|
+
function getMaintainerMapImpl() {
|
|
288
|
+
if (state.maintainerMap === null)
|
|
289
|
+
return null;
|
|
290
|
+
return Object.keys(state.maintainerMap).length === 0 ? null : { ...state.maintainerMap };
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* qfg-1t7m: source-name → Quonfig-name pairs for the report's "Environment
|
|
294
|
+
* mapping table". The source name is LD's user-facing env display name; the
|
|
295
|
+
* Quonfig name is the slugified env key. De-duped on the slugified key — two
|
|
296
|
+
* source envs that collapse to the same slug get one row (the first wins),
|
|
297
|
+
* since that is the row that will exist in quonfig.json.
|
|
298
|
+
*/
|
|
299
|
+
function getEnvironmentMapImpl() {
|
|
300
|
+
if (state.environmentSourceNames.length === 0)
|
|
301
|
+
return null;
|
|
302
|
+
const seen = new Set();
|
|
303
|
+
const entries = [];
|
|
304
|
+
for (const env of state.environmentSourceNames) {
|
|
305
|
+
const quonfigName = slugifyEnvKey(env.key);
|
|
306
|
+
if (seen.has(quonfigName))
|
|
307
|
+
continue;
|
|
308
|
+
seen.add(quonfigName);
|
|
309
|
+
entries.push({ quonfigName, sourceName: env.name });
|
|
310
|
+
}
|
|
311
|
+
return entries.length === 0 ? null : entries;
|
|
312
|
+
}
|
|
246
313
|
/**
|
|
247
314
|
* Every structured conversion note from this run — re-bucketed rollouts,
|
|
248
315
|
* dropped prerequisites, lossy individual-target conversions, etc. The write
|
|
@@ -275,6 +342,8 @@ export const launchdarklySource = {
|
|
|
275
342
|
},
|
|
276
343
|
getCommitMeta: getCommitMetaImpl,
|
|
277
344
|
getConversionNotes: getConversionNotesImpl,
|
|
345
|
+
getEnvironmentMap: getEnvironmentMapImpl,
|
|
346
|
+
getMaintainerMap: getMaintainerMapImpl,
|
|
278
347
|
getSkippedConfigs: getSkippedConfigsImpl,
|
|
279
348
|
listEnvironments: listEnvironmentsImpl,
|
|
280
349
|
name: SOURCE_NAME,
|
|
@@ -284,7 +353,9 @@ export const launchdarklySource = {
|
|
|
284
353
|
export function __resetLaunchDarklySourceForTests() {
|
|
285
354
|
state.apiKey = null;
|
|
286
355
|
state.environments = [];
|
|
356
|
+
state.environmentSourceNames = [];
|
|
287
357
|
state.fullSummary = false;
|
|
358
|
+
state.maintainerMap = null;
|
|
288
359
|
state.projectKey = resolveProjectKey();
|
|
289
360
|
state.report = new ConversionReport();
|
|
290
361
|
state.retentionHorizon = null;
|
|
@@ -4,6 +4,7 @@ export interface FrictionEntry {
|
|
|
4
4
|
exitCode: number;
|
|
5
5
|
ts: string;
|
|
6
6
|
}
|
|
7
|
+
export declare function getDefaultFrictionLogPath(home?: string): string;
|
|
7
8
|
export declare function getFrictionLogPath(envValue?: string | undefined, cwd?: string): null | string;
|
|
8
9
|
export declare function buildFrictionEntry(opts: {
|
|
9
10
|
argv?: string[];
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
2
3
|
import path from 'node:path';
|
|
4
|
+
export function getDefaultFrictionLogPath(home = os.homedir()) {
|
|
5
|
+
return path.join(home, '.qfg', 'friction.log');
|
|
6
|
+
}
|
|
3
7
|
export function getFrictionLogPath(envValue, cwd = process.cwd()) {
|
|
4
8
|
if (envValue === undefined)
|
|
5
9
|
return null;
|
|
@@ -7,7 +11,7 @@ export function getFrictionLogPath(envValue, cwd = process.cwd()) {
|
|
|
7
11
|
if (trimmed === '' || trimmed === 'false' || trimmed === '0')
|
|
8
12
|
return null;
|
|
9
13
|
if (trimmed === 'true' || trimmed === '1') {
|
|
10
|
-
return
|
|
14
|
+
return getDefaultFrictionLogPath();
|
|
11
15
|
}
|
|
12
16
|
return path.isAbsolute(trimmed) ? trimmed : path.resolve(cwd, trimmed);
|
|
13
17
|
}
|
|
@@ -23,6 +27,7 @@ export function buildFrictionEntry(opts) {
|
|
|
23
27
|
};
|
|
24
28
|
}
|
|
25
29
|
export function appendFrictionEntry(logPath, entry) {
|
|
30
|
+
fs.mkdirSync(path.dirname(logPath), { recursive: true });
|
|
26
31
|
fs.appendFileSync(logPath, JSON.stringify(entry) + '\n', 'utf8');
|
|
27
32
|
}
|
|
28
33
|
const MAX_STDERR_CAPTURE = 4000;
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare const _default: "0.0.
|
|
1
|
+
declare const _default: "0.0.51";
|
|
2
2
|
export default _default;
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// AUTO-GENERATED from package.json by scripts/generate-version.mjs — do not edit.
|
|
2
|
-
export default '0.0.
|
|
2
|
+
export default '0.0.51';
|