@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
|
@@ -2,7 +2,7 @@ import * as fs from 'node:fs';
|
|
|
2
2
|
import * as path from 'node:path';
|
|
3
3
|
import { removeQfFromGitignore, writeImportState } from './import-state.js';
|
|
4
4
|
import { buildAuditFinalCommit, buildAuditPerChangeCommits, buildMigrationCommitMessage, buildMigrationCounts, writeQuonfigFiles, } from './local-write.js';
|
|
5
|
-
import { writeMigrationReport } from './migration-report.js';
|
|
5
|
+
import { deriveFollowUpFromConversionNotes, writeMigrationReport } from './migration-report.js';
|
|
6
6
|
import { cloneAndStackPush, } from '../util/clone-and-stack-push.js';
|
|
7
7
|
import { formatResult, validateWorkspace } from '../verify/validate.js';
|
|
8
8
|
/**
|
|
@@ -95,6 +95,8 @@ export const pushMigrationToCloud = async (opts) => {
|
|
|
95
95
|
acc.duplicateResolutions =
|
|
96
96
|
resolutionEntries.length > 0 ? { entries: resolutionEntries, total: resolutionEntries.length } : null;
|
|
97
97
|
const conversionNotes = opts.source.getConversionNotes?.() ?? null;
|
|
98
|
+
const environmentMap = opts.source.getEnvironmentMap?.() ?? null;
|
|
99
|
+
const maintainerMap = opts.source.getMaintainerMap?.() ?? null;
|
|
98
100
|
const skippedTotal = (acc.skippedConfigs?.total ?? 0) + resolutionEntries.reduce((sum, r) => sum + r.deleted.length, 0);
|
|
99
101
|
const environmentsCount = opts.environments?.length ?? 0;
|
|
100
102
|
const counts = buildMigrationCounts(livePaths, environmentsCount, skippedTotal);
|
|
@@ -102,10 +104,13 @@ export const pushMigrationToCloud = async (opts) => {
|
|
|
102
104
|
const reportData = {
|
|
103
105
|
...opts.reportData,
|
|
104
106
|
counts,
|
|
107
|
+
...(environmentMap && environmentMap.length > 0 ? { environmentMap } : {}),
|
|
108
|
+
followUp: deriveFollowUpFromConversionNotes(opts.reportData.followUp, conversionNotes ?? undefined),
|
|
105
109
|
...(acc.coercedSentinels ? { coercedSentinels: acc.coercedSentinels } : {}),
|
|
106
110
|
...(conversionNotes && conversionNotes.length > 0 ? { conversionNotes } : {}),
|
|
107
111
|
...(acc.droppedOverrides ? { droppedOverrides: acc.droppedOverrides } : {}),
|
|
108
112
|
...(acc.duplicateResolutions ? { duplicateResolutions: acc.duplicateResolutions } : {}),
|
|
113
|
+
...(maintainerMap && Object.keys(maintainerMap).length > 0 ? { maintainerMap } : {}),
|
|
109
114
|
...(acc.skippedConfigs ? { skippedConfigs: acc.skippedConfigs } : {}),
|
|
110
115
|
};
|
|
111
116
|
writeMigrationReport(dir, reportData);
|
|
@@ -26,20 +26,56 @@ export type ConversionNoteCategory =
|
|
|
26
26
|
| 'dropped-maintainer'
|
|
27
27
|
/** `customProperties` dropped — report-only in v1 (D6). */
|
|
28
28
|
| 'dropped-custom-properties'
|
|
29
|
-
/**
|
|
30
|
-
|
|
29
|
+
/**
|
|
30
|
+
* `usingMobileKey:true` collapsed into Quonfig's single client-visibility bool,
|
|
31
|
+
* but the flag is still client-visible because `usingEnvironmentId` is also true.
|
|
32
|
+
* No action required — pure rollup noise.
|
|
33
|
+
*/
|
|
34
|
+
| 'dropped-mobile-key-still-visible'
|
|
35
|
+
/**
|
|
36
|
+
* `usingMobileKey:true` but `usingEnvironmentId:false` — the flag was
|
|
37
|
+
* mobile-only in LD and will no longer reach mobile clients after migration.
|
|
38
|
+
* Must-fix before cutover.
|
|
39
|
+
*/
|
|
40
|
+
| 'dropped-mobile-key-now-server-only'
|
|
31
41
|
/** A percentage rollout that will re-bucket users post-migration (plan §5.4). */
|
|
32
42
|
| 'rebucketed-rollout'
|
|
33
43
|
/** A big/synced segment whose membership is not exportable via REST. */
|
|
34
44
|
| 'unexportable-segment-membership'
|
|
35
45
|
/** An AI Config enumerated but not emitted (out of v1 scope, D9). */
|
|
36
|
-
| 'ai-config-skipped'
|
|
46
|
+
| 'ai-config-skipped'
|
|
47
|
+
/**
|
|
48
|
+
* Flagsmith D-F1: a non-boolean feature was `enabled=false`. Flagsmith would have
|
|
49
|
+
* served the SDK-side default (not visible to the migrator); Quonfig will now
|
|
50
|
+
* serve the stored `feature_state_value`. The "coerced sentinel" framing in
|
|
51
|
+
* `getCoercedSentinels()` is reused so the per-env counts surface in the report.
|
|
52
|
+
*/
|
|
53
|
+
| 'enabled-false-non-boolean'
|
|
54
|
+
/** Flagsmith D-F5: same feature has different value types across envs. Coerced to string. */
|
|
55
|
+
| 'cross-env-value-type-coerced'
|
|
56
|
+
/** Flagsmith identity override emitted as a leading PROP_IS_ONE_OF rule on the identifier attribute. */
|
|
57
|
+
| 'identity-override-as-rule'
|
|
58
|
+
/**
|
|
59
|
+
* Flagsmith identity-trait extraction: a context attribute referenced by a segment rule
|
|
60
|
+
* (plan §5.5 / D-F6). Pure FYI — your SDK callers will need to send this attribute at
|
|
61
|
+
* eval time. The `key` is the trait name; `detail` lists the segments referencing it.
|
|
62
|
+
*/
|
|
63
|
+
| 'identity-traits-referenced';
|
|
37
64
|
export interface ConversionNote {
|
|
38
65
|
category: ConversionNoteCategory;
|
|
39
66
|
/** Human-readable detail — names the exact thing lost and why. */
|
|
40
67
|
detail: string;
|
|
41
68
|
/** Source key the note is about (flag or segment key). */
|
|
42
69
|
key: string;
|
|
70
|
+
/**
|
|
71
|
+
* Structured prereq edges on `dropped-prerequisite` notes — the renderer uses
|
|
72
|
+
* this to produce the inverted "orphaned parent → downstream consumers" view
|
|
73
|
+
* (qfg-nb4n). Absent on every other category.
|
|
74
|
+
*/
|
|
75
|
+
prerequisites?: Array<{
|
|
76
|
+
parentKey: string;
|
|
77
|
+
variation: number;
|
|
78
|
+
}>;
|
|
43
79
|
}
|
|
44
80
|
/**
|
|
45
81
|
* Collects conversion notes during a migration run. A provider source holds
|
|
@@ -49,7 +85,12 @@ export interface ConversionNote {
|
|
|
49
85
|
export declare class ConversionReport {
|
|
50
86
|
private readonly notes;
|
|
51
87
|
get size(): number;
|
|
52
|
-
add(category: ConversionNoteCategory, key: string, detail: string
|
|
88
|
+
add(category: ConversionNoteCategory, key: string, detail: string, extras?: {
|
|
89
|
+
prerequisites?: Array<{
|
|
90
|
+
parentKey: string;
|
|
91
|
+
variation: number;
|
|
92
|
+
}>;
|
|
93
|
+
}): void;
|
|
53
94
|
/** All notes, in insertion order. */
|
|
54
95
|
all(): ConversionNote[];
|
|
55
96
|
/** Notes of one category, in insertion order. */
|
|
@@ -19,8 +19,11 @@ export class ConversionReport {
|
|
|
19
19
|
get size() {
|
|
20
20
|
return this.notes.length;
|
|
21
21
|
}
|
|
22
|
-
add(category, key, detail) {
|
|
23
|
-
|
|
22
|
+
add(category, key, detail, extras) {
|
|
23
|
+
const note = { category, detail, key };
|
|
24
|
+
if (extras?.prerequisites)
|
|
25
|
+
note.prerequisites = extras.prerequisites;
|
|
26
|
+
this.notes.push(note);
|
|
24
27
|
}
|
|
25
28
|
/** All notes, in insertion order. */
|
|
26
29
|
all() {
|
|
@@ -155,11 +155,10 @@ export function collapseEnvironment(env, ctx) {
|
|
|
155
155
|
return [{ criteria: [ALWAYS_TRUE], value: variationValue(env.offVariation ?? 0, ctx) }];
|
|
156
156
|
}
|
|
157
157
|
const rules = [];
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
}
|
|
158
|
+
// Prerequisites are dropped (no cross-flag dependency operator in Quonfig v1)
|
|
159
|
+
// but their reporting is owned by the caller (translateFlag), which dedupes
|
|
160
|
+
// across environments and emits one enriched note per child flag (qfg-nb4n).
|
|
161
|
+
// collapseEnvironment intentionally stays silent here.
|
|
163
162
|
// Individual + context targets evaluate before rules in LaunchDarkly, so
|
|
164
163
|
// they become leading rules. Lossy: the LD UI affordance is gone (plan §5.4).
|
|
165
164
|
const allTargets = [...(env.targets ?? []), ...(env.contextTargets ?? [])];
|
|
@@ -177,7 +176,10 @@ export function collapseEnvironment(env, ctx) {
|
|
|
177
176
|
],
|
|
178
177
|
value: variationValue(target.variation, ctx),
|
|
179
178
|
});
|
|
180
|
-
ctx.report.add('individual-target-as-rule', ctx.sourceKey, `${target.values.length} ${kind}
|
|
179
|
+
ctx.report.add('individual-target-as-rule', ctx.sourceKey, `${target.values.length} ${kind} key(s) inlined into a leading PROP_IS_ONE_OF rule on ${kind}.key. ` +
|
|
180
|
+
`**Trade-off:** you lost the LD 'targets' UI pane — adding/removing an individual now requires a ` +
|
|
181
|
+
`config edit (qfg set / web UI), not a separate UI. If this was used for beta-list-style toggling, ` +
|
|
182
|
+
`consider authoring a \`beta_user\` context attribute instead.`);
|
|
181
183
|
}
|
|
182
184
|
for (const rule of env.rules ?? []) {
|
|
183
185
|
const value = rule.rollout === undefined ? variationValue(rule.variation ?? 0, ctx) : rolloutToWeightedValues(rule.rollout, ctx);
|
package/dist/migrate/source.d.ts
CHANGED
|
@@ -12,6 +12,17 @@ export interface QuonfigFile {
|
|
|
12
12
|
deleted?: boolean;
|
|
13
13
|
path: string;
|
|
14
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* One row of the "Environment mapping table" in MIGRATION_REPORT.md. Sources
|
|
17
|
+
* implement `getEnvironmentMap()` to expose the source-name → Quonfig-name
|
|
18
|
+
* pairs they translated; the report renderer just formats the rows.
|
|
19
|
+
*/
|
|
20
|
+
export interface EnvironmentMapEntry {
|
|
21
|
+
/** The Quonfig-side environment name (slugified key). */
|
|
22
|
+
quonfigName: string;
|
|
23
|
+
/** The user-facing source-system name (e.g. LD env display name). */
|
|
24
|
+
sourceName: string;
|
|
25
|
+
}
|
|
15
26
|
export interface DroppedOverrideSummary {
|
|
16
27
|
/** Per-envId → per-output-path count of dropped override sections. */
|
|
17
28
|
byEnv: Record<string, Record<string, number>>;
|
|
@@ -106,6 +117,22 @@ export interface MigrationSource {
|
|
|
106
117
|
* report so customers can review the loss.
|
|
107
118
|
*/
|
|
108
119
|
getDroppedOverrides?(): DroppedOverrideSummary | null;
|
|
120
|
+
/**
|
|
121
|
+
* qfg-1t7m: source-name → Quonfig-name pairs for the "Environment mapping table"
|
|
122
|
+
* section of MIGRATION_REPORT.md. Returns the rows the source actually
|
|
123
|
+
* translated this run; callers thread these into report data. Sources that
|
|
124
|
+
* don't expose user-facing env names omit this and the table renders `_(none)_`.
|
|
125
|
+
*/
|
|
126
|
+
getEnvironmentMap?(): EnvironmentMapEntry[] | null;
|
|
127
|
+
/**
|
|
128
|
+
* qfg-l8uz: maintainerId → email pairs for the report's "Identifier map"
|
|
129
|
+
* section. Sources that fetch a member directory (e.g. LD's /members) expose
|
|
130
|
+
* the lookup here so the report renderer can swap opaque IDs for readable
|
|
131
|
+
* emails and decorate the `dropped-maintainer` rollup. Null when the source
|
|
132
|
+
* couldn't fetch members (auth scope, hidden member data) or has none to
|
|
133
|
+
* surface — the rest of the report still renders.
|
|
134
|
+
*/
|
|
135
|
+
getMaintainerMap?(): null | Record<string, string>;
|
|
109
136
|
/**
|
|
110
137
|
* Optional post-translate accumulator. Returns any configs that translate() soft-
|
|
111
138
|
* skipped due to invalid source data (e.g. variant/valueType mismatch). Null when
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flagsmith REST API client — Phase-1 config snapshot.
|
|
3
|
+
*
|
|
4
|
+
* Flagsmith has no bulk-export endpoint, so the fetcher stitches per-resource
|
|
5
|
+
* calls: project → environments → features → per-env featurestates → segments
|
|
6
|
+
* → feature-segments (priority) → edge identities → edge identity overrides
|
|
7
|
+
* → tags.
|
|
8
|
+
*
|
|
9
|
+
* Auth is `Authorization: Api-Key <token>` — note the `Api-Key` prefix (not
|
|
10
|
+
* `Bearer`, and not bare-token like LaunchDarkly). Rate-limiting headers are
|
|
11
|
+
* unpublished; we program against the response: back off on 429 using
|
|
12
|
+
* `Retry-After` (standard) or `X-RateLimit-Reset` (Flagsmith's variant), and
|
|
13
|
+
* step the inter-request throttle up briefly when we hit one (mirrors the
|
|
14
|
+
* Phase-2 generator's empirical backoff loop). On any non-429 error we throw
|
|
15
|
+
* immediately — only rate limiting is retried.
|
|
16
|
+
*
|
|
17
|
+
* Pagination is standard Django REST `{count, next, previous, results}` with
|
|
18
|
+
* absolute `next` URLs. Edge-identities listing is the one exception: it uses
|
|
19
|
+
* `{results, last_evaluated_key}` cursor pagination via a query-string param.
|
|
20
|
+
*/
|
|
21
|
+
import type { FlagsmithEdgeIdentity, FlagsmithEdgeIdentityOverride, FlagsmithEnvironment, FlagsmithFeature, FlagsmithFeatureSegment, FlagsmithFeatureState, FlagsmithProject, FlagsmithSegment, FlagsmithSnapshot, FlagsmithTag } from './types.js';
|
|
22
|
+
export declare function setFlagsmithBaseUrl(url: string): void;
|
|
23
|
+
export declare function selectFlagsmithBaseUrl(): string;
|
|
24
|
+
export declare function applyFlagsmithBaseUrl(): void;
|
|
25
|
+
export declare function __setSleepForTests(fn: (ms: number) => Promise<void>): void;
|
|
26
|
+
export declare function __resetSleepForTests(): void;
|
|
27
|
+
export declare class FlagsmithApiError extends Error {
|
|
28
|
+
readonly status: number;
|
|
29
|
+
constructor(status: number, statusText: string, url: string, body: string);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Single GET with `Api-Key` auth, 429 backoff against `Retry-After` /
|
|
33
|
+
* `X-RateLimit-Reset`, and an exponential fallback. Non-429 errors throw
|
|
34
|
+
* immediately — only rate limiting is retried.
|
|
35
|
+
*/
|
|
36
|
+
export declare function apiFetch(path: string, apiKey: string): Promise<unknown>;
|
|
37
|
+
/** `GET /projects/{id}/` — used as the cheap auth probe AND as a source of `use_edge_identities`. */
|
|
38
|
+
export declare function fetchProject(apiKey: string, projectId: number | string): Promise<FlagsmithProject>;
|
|
39
|
+
/** `GET /environments/?project={id}` — paginated env list. */
|
|
40
|
+
export declare function fetchEnvironments(apiKey: string, projectId: number | string): Promise<FlagsmithEnvironment[]>;
|
|
41
|
+
/**
|
|
42
|
+
* `GET /projects/{pk}/features/` — paginated feature list. Each result
|
|
43
|
+
* includes inline `multivariate_options[]` which we sort ASC by id (API6) so
|
|
44
|
+
* downstream code sees variations in definition order, not the reverse order
|
|
45
|
+
* the API returns.
|
|
46
|
+
*/
|
|
47
|
+
export declare function fetchFeatures(apiKey: string, projectId: number | string): Promise<FlagsmithFeature[]>;
|
|
48
|
+
/**
|
|
49
|
+
* `GET /features/featurestates/?environment={env_id}` — per-env featurestates.
|
|
50
|
+
*
|
|
51
|
+
* This is the unified listing that returns BOTH env-default rows
|
|
52
|
+
* (`feature_segment == null && identity == null`) AND segment-override rows
|
|
53
|
+
* (`feature_segment != null`). The plan §4.1 suggests
|
|
54
|
+
* `/environments/{api_key}/featurestates/`, but empirically on v2-versioned
|
|
55
|
+
* envs that endpoint silently omits segment-override rows. The
|
|
56
|
+
* `/features/featurestates/?environment=` path is the only read endpoint that
|
|
57
|
+
* returns both on a v2 env, so we use it uniformly across v1 and v2 envs.
|
|
58
|
+
*
|
|
59
|
+
* Identity overrides are NOT in this listing on edge-enabled projects — see
|
|
60
|
+
* `fetchEdgeIdentityOverrides`.
|
|
61
|
+
*/
|
|
62
|
+
export declare function fetchEnvFeatureStates(apiKey: string, envId: number): Promise<FlagsmithFeatureState[]>;
|
|
63
|
+
/** `GET /projects/{pk}/segments/` — project-scoped segment pool. */
|
|
64
|
+
export declare function fetchSegments(apiKey: string, projectId: number | string): Promise<FlagsmithSegment[]>;
|
|
65
|
+
/**
|
|
66
|
+
* `GET /features/feature-segments/?environment={env_id}&feature={feature_id}` —
|
|
67
|
+
* the per-env, per-feature segment priority list. Required by the converter
|
|
68
|
+
* to order the segment-override rules (Quonfig is a strict first-match list).
|
|
69
|
+
*
|
|
70
|
+
* The endpoint REJECTS calls without `feature` (`{feature: ["This field is
|
|
71
|
+
* required."]}`), so we have to walk every feature × env combo. This is N×E
|
|
72
|
+
* calls in the worst case — acceptable for the corpus (~110 features × 2 envs
|
|
73
|
+
* = ~220 calls) and roughly the same order as fetchEnvFeatureStates.
|
|
74
|
+
*/
|
|
75
|
+
export declare function fetchFeatureSegments(apiKey: string, envId: number, featureId: number): Promise<FlagsmithFeatureSegment[]>;
|
|
76
|
+
/**
|
|
77
|
+
* `GET /environments/{api_key}/edge-identities/` — paginated edge-identity
|
|
78
|
+
* listing. Pagination here is CURSOR-based (`last_evaluated_key`) not page
|
|
79
|
+
* numbers — the Flagsmith dynamo-backed edge store can't offset-paginate.
|
|
80
|
+
*
|
|
81
|
+
* The fetcher uses this only to surface identifiers for the report; the
|
|
82
|
+
* actual identity-override featurestates come from
|
|
83
|
+
* `fetchEdgeIdentityOverrides`, which is a one-call-per-env shortcut.
|
|
84
|
+
*/
|
|
85
|
+
export declare function fetchEdgeIdentities(apiKey: string, envApiKey: string): Promise<FlagsmithEdgeIdentity[]>;
|
|
86
|
+
/**
|
|
87
|
+
* `GET /environments/{api_key}/edge-identity-overrides` — every identity
|
|
88
|
+
* override in the env, flat. Each row is `{identifier, identity_uuid,
|
|
89
|
+
* feature_state}` so we can index by feature without walking N identities.
|
|
90
|
+
*
|
|
91
|
+
* Note: the path has NO trailing slash (unlike most Flagsmith paths). This
|
|
92
|
+
* endpoint does not paginate in the verified shape — it returns
|
|
93
|
+
* `{results: [...]}` directly.
|
|
94
|
+
*/
|
|
95
|
+
export declare function fetchEdgeIdentityOverrides(apiKey: string, envApiKey: string): Promise<FlagsmithEdgeIdentityOverride[]>;
|
|
96
|
+
/** `GET /projects/{pk}/tags/` — project-scoped tag pool. */
|
|
97
|
+
export declare function fetchTags(apiKey: string, projectId: number | string): Promise<FlagsmithTag[]>;
|
|
98
|
+
/**
|
|
99
|
+
* Stitch the whole Phase-1 snapshot: project → environments → features →
|
|
100
|
+
* per-env featurestates (split into env-default + segment-overrides) → segments
|
|
101
|
+
* → feature-segments (per env, per feature with segment overrides) → edge
|
|
102
|
+
* identity overrides → tags.
|
|
103
|
+
*
|
|
104
|
+
* Walk count grows as 1 (project) + 1 (envs) + 1 (segments) + 1 (tags) + E ×
|
|
105
|
+
* (1 featurestates + 1 edge-id-overrides + S features-with-segov ×
|
|
106
|
+
* feature-segments). For the 110-feature, 2-env, 5 features-with-segov live
|
|
107
|
+
* corpus: ~1 + 1 + 1 + 1 + 2 × (1 + 1 + 5) = ~17 calls. The featurestates
|
|
108
|
+
* endpoint is paginated under 100/page so larger projects multiply that
|
|
109
|
+
* featurestates leg.
|
|
110
|
+
*
|
|
111
|
+
* `sinceEpochMs` is currently unused — decision D2 says Phase 1 is always a
|
|
112
|
+
* full re-snapshot. It survives in the signature so reporting (Epic 5) can
|
|
113
|
+
* compute "what's new since last run" against the same snapshot.
|
|
114
|
+
*/
|
|
115
|
+
export declare function fetchSnapshot(apiKey: string, projectId: number | string): Promise<FlagsmithSnapshot>;
|