@withandeo/cli 0.19.10 → 0.19.11
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/dist/api.d.ts +0 -1
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +0 -2
- package/dist/api.js.map +1 -1
- package/dist/contracts.d.ts +8 -0
- package/dist/contracts.d.ts.map +1 -1
- package/dist/flagship-browser.d.ts +18 -0
- package/dist/flagship-browser.d.ts.map +1 -0
- package/dist/flagship-browser.js +63 -0
- package/dist/flagship-browser.js.map +1 -0
- package/dist/flagship-contract.d.ts +4 -37
- package/dist/flagship-contract.d.ts.map +1 -1
- package/dist/flagship-contract.js +23 -65
- package/dist/flagship-contract.js.map +1 -1
- package/dist/flagship-definition.d.ts +67 -0
- package/dist/flagship-definition.d.ts.map +1 -0
- package/dist/flagship-definition.js +207 -0
- package/dist/flagship-definition.js.map +1 -0
- package/dist/flagship-runtime.d.ts +57 -0
- package/dist/flagship-runtime.d.ts.map +1 -0
- package/dist/flagship-runtime.js +79 -0
- package/dist/flagship-runtime.js.map +1 -0
- package/dist/main.js +74 -38
- package/dist/main.js.map +1 -1
- package/package.json +7 -2
- package/skill/publication.json +1 -1
- package/skill/tender-accounts/SKILL.md +1 -1
- package/skill/tender-accounts/references/feature-flags.md +140 -69
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/** Admission and preset compilation only. Cloudflare owns evaluation and hashing.
|
|
2
|
+
* Wire contract pinned to wrangler 4.135.0 (src/flagship/client.ts, shared.ts).
|
|
3
|
+
*/
|
|
4
|
+
export const FLAGSHIP_SCHEMA = "cloudflare/wrangler@4.135.0";
|
|
5
|
+
export const FLAG_OPERATORS = ["equals", "not_equals", "greater_than", "less_than", "greater_than_or_equals", "less_than_or_equals", "contains", "starts_with", "ends_with", "in", "not_in"];
|
|
6
|
+
export class FlagCompatibilityError extends Error {
|
|
7
|
+
field;
|
|
8
|
+
code = "flagship_definition_invalid";
|
|
9
|
+
constructor(field, detail) {
|
|
10
|
+
super(`${field}: ${detail}`);
|
|
11
|
+
this.field = field;
|
|
12
|
+
this.name = "FlagCompatibilityError";
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
const object = (v) => !!v && typeof v === "object" && !Array.isArray(v);
|
|
16
|
+
const badKeys = new Set(["__proto__", "prototype", "constructor"]);
|
|
17
|
+
function fail(path, message) { throw new FlagCompatibilityError(path, message); }
|
|
18
|
+
function fields(value, allowed, path) {
|
|
19
|
+
for (const key of Object.keys(value))
|
|
20
|
+
if (!allowed.includes(key))
|
|
21
|
+
fail(`${path}.${key}`, "Unsupported field for pinned provider schema; export and resolve compatibility before editing.");
|
|
22
|
+
}
|
|
23
|
+
function name(value, path, pattern = /^[A-Za-z0-9_-]{1,64}$/) {
|
|
24
|
+
if (typeof value !== "string" || !pattern.test(value) || badKeys.has(value))
|
|
25
|
+
fail(path, "Invalid name (maximum 64 characters).");
|
|
26
|
+
}
|
|
27
|
+
export function flagValueType(value) {
|
|
28
|
+
return typeof value === "boolean" ? "boolean" : typeof value === "string" ? "string" : typeof value === "number" ? "number" : "json";
|
|
29
|
+
}
|
|
30
|
+
/** Bound JSON before traversing or serializing. Do not coerce or change key/value case. */
|
|
31
|
+
export function safeFlagJson(value, path = "$", depth = 0) {
|
|
32
|
+
if (depth > 32)
|
|
33
|
+
fail(path, "JSON nesting exceeds 32 levels.");
|
|
34
|
+
if (value === null || typeof value === "string" || typeof value === "boolean")
|
|
35
|
+
return;
|
|
36
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
37
|
+
return;
|
|
38
|
+
if (Array.isArray(value)) {
|
|
39
|
+
value.forEach((v, i) => safeFlagJson(v, `${path}[${i}]`, depth + 1));
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (object(value) && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null)) {
|
|
43
|
+
for (const [k, v] of Object.entries(value)) {
|
|
44
|
+
if (badKeys.has(k))
|
|
45
|
+
fail(`${path}.${k}`, "Unsafe object key.");
|
|
46
|
+
safeFlagJson(v, `${path}.${k}`, depth + 1);
|
|
47
|
+
}
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
fail(path, "Expected finite JSON data.");
|
|
51
|
+
}
|
|
52
|
+
export function flagContext(value = {}) {
|
|
53
|
+
if (!object(value) || Object.keys(value).length > 64)
|
|
54
|
+
fail("context", "Expected at most 64 scalar attributes.");
|
|
55
|
+
safeFlagJson(value, "context");
|
|
56
|
+
for (const [k, v] of Object.entries(value)) {
|
|
57
|
+
if (!k || k.length > 64 || /[\x00-\x1f\x7f]/.test(k))
|
|
58
|
+
fail(`context.${k}`, "Invalid attribute name.");
|
|
59
|
+
if (!["string", "boolean", "number"].includes(typeof v))
|
|
60
|
+
fail(`context.${k}`, "Context values must be string, number, or boolean.");
|
|
61
|
+
if (typeof v === "string" && v.length > 1024)
|
|
62
|
+
fail(`context.${k}`, "Context string exceeds 1024 characters.");
|
|
63
|
+
}
|
|
64
|
+
if (new TextEncoder().encode(JSON.stringify(value)).length > 16 * 1024)
|
|
65
|
+
fail("context", "Context exceeds 16 KB.");
|
|
66
|
+
return structuredClone(value);
|
|
67
|
+
}
|
|
68
|
+
function condition(value, path, depth) {
|
|
69
|
+
if (!object(value))
|
|
70
|
+
fail(path, "Expected condition object.");
|
|
71
|
+
if (depth > 5)
|
|
72
|
+
fail(path, "Provider condition nesting exceeds five levels.");
|
|
73
|
+
if ("logical_operator" in value || "clauses" in value) {
|
|
74
|
+
fields(value, ["logical_operator", "clauses"], path);
|
|
75
|
+
if (value.logical_operator !== "AND" && value.logical_operator !== "OR")
|
|
76
|
+
fail(`${path}.logical_operator`, "Expected AND or OR.");
|
|
77
|
+
if (!Array.isArray(value.clauses) || !value.clauses.length)
|
|
78
|
+
fail(`${path}.clauses`, "Expected nonempty clauses.");
|
|
79
|
+
value.clauses.forEach((v, i) => condition(v, `${path}.clauses[${i}]`, depth + 1));
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
fields(value, ["attribute", "operator", "value"], path);
|
|
83
|
+
name(value.attribute, `${path}.attribute`, /^[^\x00-\x1f\x7f]{1,64}$/);
|
|
84
|
+
if (!FLAG_OPERATORS.includes(value.operator))
|
|
85
|
+
fail(`${path}.operator`, "Unknown provider operator.");
|
|
86
|
+
const operand = (v) => typeof v === "string" && v.length <= 256 || typeof v === "number" && Number.isFinite(v);
|
|
87
|
+
if (value.operator === "in" || value.operator === "not_in") {
|
|
88
|
+
if (!Array.isArray(value.value) || !value.value.every(operand))
|
|
89
|
+
fail(`${path}.value`, "Membership requires an array of strings or finite numbers.");
|
|
90
|
+
}
|
|
91
|
+
else if (["greater_than", "less_than", "greater_than_or_equals", "less_than_or_equals"].includes(value.operator)) {
|
|
92
|
+
if (!(typeof value.value === "number" && Number.isFinite(value.value)) && !(typeof value.value === "string" && value.value.length <= 256 && /^\d{4}-\d\d-\d\dT/.test(value.value) && Number.isFinite(Date.parse(value.value))))
|
|
93
|
+
fail(`${path}.value`, "Comparison requires a finite number or ISO 8601 datetime.");
|
|
94
|
+
}
|
|
95
|
+
else if (typeof value.value !== "string" || value.value.length > 256)
|
|
96
|
+
fail(`${path}.value`, "This provider operator requires a string of at most 256 characters.");
|
|
97
|
+
}
|
|
98
|
+
/** One pinned provider schema applies to every flag definition. */
|
|
99
|
+
export function validateFlagDefinition(value) {
|
|
100
|
+
safeFlagJson(value);
|
|
101
|
+
if (!object(value))
|
|
102
|
+
fail("$", "Expected a flag definition.");
|
|
103
|
+
fields(value, ["key", "description", "enabled", "default_variation", "variations", "rules"], "$");
|
|
104
|
+
name(value.key, "$.key");
|
|
105
|
+
if (value.description !== undefined && (typeof value.description !== "string" || value.description.length > 512))
|
|
106
|
+
fail("$.description", "Expected at most 512 characters.");
|
|
107
|
+
if (typeof value.enabled !== "boolean")
|
|
108
|
+
fail("$.enabled", "Expected boolean.");
|
|
109
|
+
if (!object(value.variations) || !Object.keys(value.variations).length)
|
|
110
|
+
fail("$.variations", "Expected named variants.");
|
|
111
|
+
let type;
|
|
112
|
+
for (const [k, v] of Object.entries(value.variations)) {
|
|
113
|
+
name(k, `$.variations.${k}`);
|
|
114
|
+
const actual = flagValueType(v);
|
|
115
|
+
if (type && actual !== type)
|
|
116
|
+
fail(`$.variations.${k}`, "All variants must share one value type.");
|
|
117
|
+
type = actual;
|
|
118
|
+
// Conservative decimal KB until provider byte-unit enforcement is hosted-verified.
|
|
119
|
+
if (new TextEncoder().encode(JSON.stringify(v)).length > 10000)
|
|
120
|
+
fail(`$.variations.${k}`, "Serialized variant exceeds 10 KB.");
|
|
121
|
+
}
|
|
122
|
+
if (typeof value.default_variation !== "string" || !Object.hasOwn(value.variations, value.default_variation))
|
|
123
|
+
fail("$.default_variation", "Default must reference a variant.");
|
|
124
|
+
if (!Array.isArray(value.rules))
|
|
125
|
+
fail("$.rules", "Expected ordered rules.");
|
|
126
|
+
const priorities = new Set();
|
|
127
|
+
value.rules.forEach((r, i) => {
|
|
128
|
+
const path = `$.rules[${i}]`;
|
|
129
|
+
if (!object(r))
|
|
130
|
+
fail(path, "Expected rule object.");
|
|
131
|
+
fields(r, ["priority", "conditions", "serve_variation", "rollout"], path);
|
|
132
|
+
if (typeof r.priority !== "number" || !Number.isSafeInteger(r.priority) || r.priority < 1 || priorities.has(r.priority))
|
|
133
|
+
fail(`${path}.priority`, "Expected unique positive integer priority.");
|
|
134
|
+
priorities.add(r.priority);
|
|
135
|
+
if (typeof r.serve_variation !== "string" || !Object.hasOwn(value.variations, r.serve_variation))
|
|
136
|
+
fail(`${path}.serve_variation`, "Rule must reference a variant.");
|
|
137
|
+
if (!Array.isArray(r.conditions))
|
|
138
|
+
fail(`${path}.conditions`, "Expected condition array.");
|
|
139
|
+
r.conditions.forEach((c, j) => condition(c, `${path}.conditions[${j}]`, 1));
|
|
140
|
+
if (r.rollout !== undefined) {
|
|
141
|
+
if (!object(r.rollout))
|
|
142
|
+
fail(`${path}.rollout`, "Expected rollout object.");
|
|
143
|
+
fields(r.rollout, ["percentage", "attribute"], `${path}.rollout`);
|
|
144
|
+
if (typeof r.rollout.percentage !== "number" || !Number.isFinite(r.rollout.percentage) || r.rollout.percentage < 0 || r.rollout.percentage > 100
|
|
145
|
+
|| Number(r.rollout.percentage.toFixed(2)) !== r.rollout.percentage)
|
|
146
|
+
fail(`${path}.rollout.percentage`, "Expected percentage from 0 through 100 with at most two decimal places.");
|
|
147
|
+
if (r.rollout.attribute !== undefined)
|
|
148
|
+
name(r.rollout.attribute, `${path}.rollout.attribute`, /^[^\x00-\x1f\x7f]{1,64}$/);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
if (new TextEncoder().encode(JSON.stringify(value)).length > 192 * 1024)
|
|
152
|
+
fail("$", "Andeo definition admission limit is 192 KB (separate from provider app quota).");
|
|
153
|
+
return structuredClone(value);
|
|
154
|
+
}
|
|
155
|
+
/** Known read-only metadata is stripped only for writes. Unknown fields survive inspection. */
|
|
156
|
+
export function observedFlagDefinition(value) {
|
|
157
|
+
if (!object(value))
|
|
158
|
+
fail("$", "Invalid provider response.");
|
|
159
|
+
const metadata = new Set(["id", "type", "created_at", "updated_at", "updated_by", "created_on", "modified_on", "app_id", "version"]);
|
|
160
|
+
return Object.fromEntries(Object.entries(value).filter(([k, v]) => !metadata.has(k) && v !== undefined && !(k === "description" && v === null)));
|
|
161
|
+
}
|
|
162
|
+
export function canonicalFlag(value) {
|
|
163
|
+
const sort = (v) => Array.isArray(v) ? v.map(sort) : object(v) ? Object.fromEntries(Object.keys(v).sort().map(k => [k, sort(v[k])])) : v;
|
|
164
|
+
return JSON.stringify(sort(value));
|
|
165
|
+
}
|
|
166
|
+
export function flagDiff(before, after) {
|
|
167
|
+
if (!object(before) || !object(after))
|
|
168
|
+
return canonicalFlag(before) === canonicalFlag(after) ? [] : [{ field: "$", before, after }];
|
|
169
|
+
return [...new Set([...Object.keys(before), ...Object.keys(after)])].filter(k => canonicalFlag(before[k]) !== canonicalFlag(after[k])).map(k => ({ field: k, before: before[k], after: after[k] }));
|
|
170
|
+
}
|
|
171
|
+
export function compileFlagSplit(definition, split) {
|
|
172
|
+
name(split.attribute, "split.attribute", /^[^\x00-\x1f\x7f]{1,64}$/);
|
|
173
|
+
if (split.shares.length < 2 || new Set(split.shares.map(s => s.variant)).size !== split.shares.length)
|
|
174
|
+
fail("split.shares", "Expected at least two distinct variants.");
|
|
175
|
+
let threshold = 0;
|
|
176
|
+
const rules = split.shares.map((s, i) => {
|
|
177
|
+
if (!Number.isFinite(s.percentage) || s.percentage <= 0 || s.percentage > 100 || Number(s.percentage.toFixed(2)) !== s.percentage)
|
|
178
|
+
fail(`split.shares[${i}].percentage`, "Shares must be positive percentages with at most two decimals.");
|
|
179
|
+
threshold = Math.round((threshold + s.percentage) * 100) / 100;
|
|
180
|
+
return { priority: i + 1, conditions: structuredClone(split.conditions), serve_variation: s.variant, rollout: { percentage: threshold, attribute: split.attribute } };
|
|
181
|
+
});
|
|
182
|
+
if (threshold !== 100)
|
|
183
|
+
fail("split.shares", "Shares must total 100% of eligible traffic. Partial enrollment requires advanced rules.");
|
|
184
|
+
return validateFlagDefinition({ ...definition, rules });
|
|
185
|
+
}
|
|
186
|
+
export function parseFlagSplit(definition) {
|
|
187
|
+
if (definition.rules.length < 2)
|
|
188
|
+
return null;
|
|
189
|
+
const first = definition.rules[0];
|
|
190
|
+
if (!first.rollout?.attribute)
|
|
191
|
+
return null;
|
|
192
|
+
let previous = 0;
|
|
193
|
+
const variants = new Set(), shares = [];
|
|
194
|
+
for (const [i, r] of definition.rules.entries()) {
|
|
195
|
+
if (r.priority !== i + 1 || !r.rollout || r.rollout.attribute !== first.rollout.attribute || canonicalFlag(r.conditions) !== canonicalFlag(first.conditions)
|
|
196
|
+
|| r.rollout.percentage <= previous || variants.has(r.serve_variation))
|
|
197
|
+
return null;
|
|
198
|
+
shares.push({ variant: r.serve_variation, percentage: Math.round((r.rollout.percentage - previous) * 100) / 100 });
|
|
199
|
+
previous = r.rollout.percentage;
|
|
200
|
+
variants.add(r.serve_variation);
|
|
201
|
+
}
|
|
202
|
+
return previous === 100 ? { attribute: first.rollout.attribute, conditions: structuredClone(first.conditions), shares } : null;
|
|
203
|
+
}
|
|
204
|
+
export function globalBooleanFlag(key) {
|
|
205
|
+
return validateFlagDefinition({ key, enabled: false, default_variation: "off", variations: { off: false, on: true }, rules: [{ priority: 1, conditions: [], serve_variation: "on" }] });
|
|
206
|
+
}
|
|
207
|
+
//# sourceMappingURL=flagship-definition.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flagship-definition.js","sourceRoot":"","sources":["../src/flagship-definition.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { type FlagContext, type FlagValue, type FlagValueType } from "./flagship-contract.js";
|
|
2
|
+
export interface NativeFlagDetails<T = FlagValue> {
|
|
3
|
+
flagKey: string;
|
|
4
|
+
value: T;
|
|
5
|
+
variant?: string;
|
|
6
|
+
reason?: string;
|
|
7
|
+
errorCode?: string;
|
|
8
|
+
}
|
|
9
|
+
/** Structural native binding contract; no rules, hashes or provider credentials. */
|
|
10
|
+
export interface NativeFlags {
|
|
11
|
+
getBooleanDetails(key: string, fallback: boolean, context?: FlagContext): Promise<NativeFlagDetails<boolean>>;
|
|
12
|
+
getStringDetails(key: string, fallback: string, context?: FlagContext): Promise<NativeFlagDetails<string>>;
|
|
13
|
+
getNumberDetails(key: string, fallback: number, context?: FlagContext): Promise<NativeFlagDetails<number>>;
|
|
14
|
+
getObjectDetails<T>(key: string, fallback: T, context?: FlagContext): Promise<NativeFlagDetails<T>>;
|
|
15
|
+
}
|
|
16
|
+
export interface ClientVisibleFlag {
|
|
17
|
+
key: string;
|
|
18
|
+
type: FlagValueType;
|
|
19
|
+
fallback: FlagValue;
|
|
20
|
+
}
|
|
21
|
+
export interface FlagBootstrap {
|
|
22
|
+
schema: "andeo.flag-bootstrap/v1";
|
|
23
|
+
environment: string;
|
|
24
|
+
refreshAfterMs: number;
|
|
25
|
+
values: Record<string, NativeFlagDetails>;
|
|
26
|
+
}
|
|
27
|
+
export declare function nativeFlagDetails(binding: NativeFlags, flag: ClientVisibleFlag, context: FlagContext): Promise<NativeFlagDetails>;
|
|
28
|
+
/** Application-owned GET endpoint/SSR bootstrap. The server chooses scope and keys;
|
|
29
|
+
* resolveContext must authenticate/enrich trusted properties and select only
|
|
30
|
+
* designated presentation attributes. No management credential or private rules.
|
|
31
|
+
*/
|
|
32
|
+
export declare function createFlagGateway(input: {
|
|
33
|
+
binding: NativeFlags;
|
|
34
|
+
environment: string;
|
|
35
|
+
flags: readonly ClientVisibleFlag[];
|
|
36
|
+
resolveContext: (request: Request) => FlagContext | Promise<FlagContext>;
|
|
37
|
+
refreshAfterMs?: number;
|
|
38
|
+
}): (request: Request) => Promise<Response>;
|
|
39
|
+
export interface FlagExposure {
|
|
40
|
+
flagKey: string;
|
|
41
|
+
variant: string;
|
|
42
|
+
reason?: string;
|
|
43
|
+
environment: string;
|
|
44
|
+
experimentVersion: string;
|
|
45
|
+
}
|
|
46
|
+
/** Call only when the application actually shows an experience. Caller chooses
|
|
47
|
+
* deduplication lifetime and measurement destination; no raw context is emitted.
|
|
48
|
+
*/
|
|
49
|
+
export declare function flagExposure(details: NativeFlagDetails, environment: string, experimentVersion: string): FlagExposure | null;
|
|
50
|
+
/** Optional server-owned one-year anonymous cookie. Reuse on browser and server
|
|
51
|
+
* actions; login does not change it. Explicit reset returns a new identity.
|
|
52
|
+
*/
|
|
53
|
+
export declare function flagVisitorIdentity(request: Request, reset?: boolean): {
|
|
54
|
+
targetingKey: string;
|
|
55
|
+
setCookie?: string;
|
|
56
|
+
};
|
|
57
|
+
//# sourceMappingURL=flagship-runtime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flagship-runtime.d.ts","sourceRoot":"","sources":["../src/flagship-runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqD,KAAK,WAAW,EAAE,KAAK,SAAS,EAAE,KAAK,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAEjJ,MAAM,WAAW,iBAAiB,CAAC,CAAC,GAAG,SAAS;IAAI,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,CAAC,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE;AACtI,oFAAoF;AACpF,MAAM,WAAW,WAAW;IAC1B,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAAC,CAAC;IAC9G,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAC;IAC3G,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAC;IAC3G,gBAAgB,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAC;CACrG;AACD,MAAM,WAAW,iBAAiB;IAAG,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,aAAa,CAAC;IAAC,QAAQ,EAAE,SAAS,CAAA;CAAE;AAC5F,MAAM,WAAW,aAAa;IAAG,MAAM,EAAE,yBAAyB,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAA;CAAE;AAC5J,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,iBAAiB,EAAE,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,iBAAiB,CAAC,CASvI;AACD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE;IAAE,OAAO,EAAE,WAAW,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,SAAS,iBAAiB,EAAE,CAAC;IACxH,cAAc,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,CAAA;CAAE,aAO5E,OAAO,KAAG,OAAO,CAAC,QAAQ,CAAC,CAmBnD;AACD,MAAM,WAAW,YAAY;IAAG,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,iBAAiB,EAAE,MAAM,CAAA;CAAE;AACnI;;GAEG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAG5H;AACD;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,UAAQ,GAAG;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAOjH"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { flagKey, flagContext, flagValueType, safeFlagJson } from "./flagship-contract.js";
|
|
2
|
+
export async function nativeFlagDetails(binding, flag, context) {
|
|
3
|
+
safeFlagJson(flag.fallback);
|
|
4
|
+
if (flagValueType(flag.fallback) !== flag.type)
|
|
5
|
+
throw new Error("flagship_fallback_type_invalid");
|
|
6
|
+
switch (flag.type) {
|
|
7
|
+
case "boolean": return binding.getBooleanDetails(flag.key, flag.fallback, context);
|
|
8
|
+
case "string": return binding.getStringDetails(flag.key, flag.fallback, context);
|
|
9
|
+
case "number": return binding.getNumberDetails(flag.key, flag.fallback, context);
|
|
10
|
+
case "json": return binding.getObjectDetails(flag.key, flag.fallback, context);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/** Application-owned GET endpoint/SSR bootstrap. The server chooses scope and keys;
|
|
14
|
+
* resolveContext must authenticate/enrich trusted properties and select only
|
|
15
|
+
* designated presentation attributes. No management credential or private rules.
|
|
16
|
+
*/
|
|
17
|
+
export function createFlagGateway(input) {
|
|
18
|
+
if (input.flags.length > 64 || new Set(input.flags.map(f => f.key)).size !== input.flags.length)
|
|
19
|
+
throw new Error("flagship_browser_allowlist_invalid");
|
|
20
|
+
safeFlagJson(input.flags);
|
|
21
|
+
for (const flag of input.flags) {
|
|
22
|
+
flagKey(flag.key);
|
|
23
|
+
if (flagValueType(flag.fallback) !== flag.type)
|
|
24
|
+
throw new Error("flagship_browser_allowlist_invalid");
|
|
25
|
+
}
|
|
26
|
+
const flags = structuredClone(input.flags), { binding, environment, resolveContext } = input;
|
|
27
|
+
const refreshAfterMs = input.refreshAfterMs ?? 30000;
|
|
28
|
+
if (!Number.isSafeInteger(refreshAfterMs) || refreshAfterMs < 0 || refreshAfterMs > 60000)
|
|
29
|
+
throw new Error("flagship_refresh_invalid");
|
|
30
|
+
return async (request) => {
|
|
31
|
+
if (request.method !== "GET")
|
|
32
|
+
return new Response(null, { status: 405, headers: { allow: "GET" } });
|
|
33
|
+
// Queries never select flags, apps, environments or trusted targeting properties.
|
|
34
|
+
if (new URL(request.url).search)
|
|
35
|
+
return new Response(null, { status: 400 });
|
|
36
|
+
const context = flagContext(await resolveContext(request));
|
|
37
|
+
const entries = await Promise.all(flags.map(async (flag) => {
|
|
38
|
+
let details;
|
|
39
|
+
try {
|
|
40
|
+
details = await nativeFlagDetails(binding, flag, context);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
details = { flagKey: flag.key, value: flag.fallback, reason: "ERROR", errorCode: "GENERAL" };
|
|
44
|
+
}
|
|
45
|
+
// Explicitly select the public details; never spread provider metadata.
|
|
46
|
+
const publicDetails = { flagKey: flag.key, value: details.value, ...(details.variant ? { variant: details.variant } : {}),
|
|
47
|
+
...(details.reason ? { reason: details.reason } : {}), ...(details.errorCode ? { errorCode: details.errorCode } : {}) };
|
|
48
|
+
safeFlagJson(publicDetails);
|
|
49
|
+
return [flag.key, publicDetails];
|
|
50
|
+
}));
|
|
51
|
+
const bootstrap = { schema: "andeo.flag-bootstrap/v1", environment, refreshAfterMs, values: Object.fromEntries(entries) };
|
|
52
|
+
const body = JSON.stringify(bootstrap);
|
|
53
|
+
if (new TextEncoder().encode(body).length > 64 * 1024)
|
|
54
|
+
throw new Error("flagship_bootstrap_size_exceeded");
|
|
55
|
+
return new Response(body, { headers: { "content-type": "application/json", "cache-control": "private, no-store", vary: "Cookie, Authorization" } });
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/** Call only when the application actually shows an experience. Caller chooses
|
|
59
|
+
* deduplication lifetime and measurement destination; no raw context is emitted.
|
|
60
|
+
*/
|
|
61
|
+
export function flagExposure(details, environment, experimentVersion) {
|
|
62
|
+
if (details.errorCode || details.reason === "ERROR" || details.reason === "DISABLED" || details.reason === "DEFAULT" || !details.variant || !experimentVersion)
|
|
63
|
+
return null;
|
|
64
|
+
return { flagKey: details.flagKey, variant: details.variant, reason: details.reason, environment, experimentVersion };
|
|
65
|
+
}
|
|
66
|
+
/** Optional server-owned one-year anonymous cookie. Reuse on browser and server
|
|
67
|
+
* actions; login does not change it. Explicit reset returns a new identity.
|
|
68
|
+
*/
|
|
69
|
+
export function flagVisitorIdentity(request, reset = false) {
|
|
70
|
+
const existing = request.headers.get("cookie")?.split(";").map(s => s.trim()).find(s => s.startsWith("andeo_flag_visitor="))?.slice(19);
|
|
71
|
+
if (!reset && existing && /^[a-f0-9-]{36}$/.test(existing))
|
|
72
|
+
return { targetingKey: existing };
|
|
73
|
+
const url = new URL(request.url), loopback = ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname);
|
|
74
|
+
if (url.protocol !== "https:" && !loopback)
|
|
75
|
+
throw new Error("flagship_identity_https_required");
|
|
76
|
+
const targetingKey = crypto.randomUUID();
|
|
77
|
+
return { targetingKey, setCookie: `andeo_flag_visitor=${targetingKey}; Path=/; Max-Age=31536000; HttpOnly; SameSite=Lax${url.protocol === "https:" ? "; Secure" : ""}` };
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=flagship-runtime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flagship-runtime.js","sourceRoot":"","sources":["../src/flagship-runtime.ts"],"names":[],"mappings":"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"}
|
package/dist/main.js
CHANGED
|
@@ -20,7 +20,7 @@ import { initializeStarter } from "./starter.js";
|
|
|
20
20
|
import { normalizeAndeoEnvironment } from "./andeo-environment.js";
|
|
21
21
|
import { DOC_TOPICS, readDocumentation, installDocumentation, installedSkillCheck } from "./documentation.js";
|
|
22
22
|
import { capabilityPreflight } from "./capability-preflight.js";
|
|
23
|
-
import { flagEnvironment, flagKey,
|
|
23
|
+
import { flagEnvironment, flagKey, validateFlagDefinition, flagContext, FLAGSHIP_SCHEMA, FLAG_DELETION_GUIDANCE, FlagCompatibilityError } from "./flagship-contract.js";
|
|
24
24
|
const packageMetadata = createRequire(import.meta.url)("../package.json");
|
|
25
25
|
if (typeof packageMetadata.version !== "string" || packageMetadata.version.length === 0) {
|
|
26
26
|
throw new Error("Andeo CLI package version is missing.");
|
|
@@ -98,6 +98,16 @@ export async function runCli(argv, overrides = {}) {
|
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
100
|
async function execute(topic, options, cwd, runtime) {
|
|
101
|
+
if (topic === "artifact finalize") {
|
|
102
|
+
const directory = resolve(cwd, requiredFlag(options, "artifact-directory"));
|
|
103
|
+
const before = await verifyArtifact(directory);
|
|
104
|
+
await packageAppWorkflows(cwd, before.directory, runtime.environment.WRANGLER_ENV);
|
|
105
|
+
const after = await verifyArtifact(directory);
|
|
106
|
+
if (before.envelope.sourceRevision !== after.envelope.sourceRevision)
|
|
107
|
+
throw new CliError("artifact_source_changed", "Packaging changed the attested source revision.");
|
|
108
|
+
return { value: { status: "verified", sourceRevision: after.envelope.sourceRevision, bundleDigest: after.envelope.bundleDigest,
|
|
109
|
+
artifactDirectory: after.directory, workflowCount: after.envelope.manifest.workflows?.declarations.length ?? 0, flags: after.envelope.manifest.flags ?? null } };
|
|
110
|
+
}
|
|
101
111
|
if (topic.startsWith("workflows "))
|
|
102
112
|
return { value: await workflowCommand(topic.slice(10), cwd, options, runtime) };
|
|
103
113
|
if (topic.startsWith("email "))
|
|
@@ -2467,6 +2477,10 @@ function exampleConfig() {
|
|
|
2467
2477
|
};
|
|
2468
2478
|
}
|
|
2469
2479
|
async function flagshipCommand(command, cwd, options, runtime) {
|
|
2480
|
+
const definitionFile = async () => validateFlagDefinition(JSON.parse(new TextDecoder().decode(await readBoundedFile(resolve(cwd, requiredFlag(options, "file")), 192 * 1024))));
|
|
2481
|
+
if (command === "flags validate")
|
|
2482
|
+
return { schema: FLAGSHIP_SCHEMA, definition: await definitionFile() };
|
|
2483
|
+
const createDefinition = command === "flags create" ? await definitionFile() : undefined;
|
|
2470
2484
|
let environment;
|
|
2471
2485
|
try {
|
|
2472
2486
|
environment = flagEnvironment(requiredFlag(options, "env"));
|
|
@@ -2479,21 +2493,33 @@ async function flagshipCommand(command, cwd, options, runtime) {
|
|
|
2479
2493
|
if (!projectId)
|
|
2480
2494
|
throw new CliError("project_required", "Link a project or pass --project.");
|
|
2481
2495
|
const { client } = await authenticatedClient(cwd, context?.link ?? null, options, runtime);
|
|
2482
|
-
if (command
|
|
2483
|
-
return client.flagship(projectId, ["apps", ...(command === "apps ensure" ? ["ensure"] : [])], environment, command === "apps ensure" ? { body: {} } : {});
|
|
2496
|
+
if (command === "apps ensure" || command === "apps list")
|
|
2497
|
+
return client.flagship(projectId, ["apps", ...(command === "apps ensure" ? ["ensure"] : [])], environment, command === "apps ensure" ? { body: { ...(options.flags.has("collection") ? { collection: options.flags.get("collection") } : {}) } } : {});
|
|
2498
|
+
if (command.startsWith("apps ")) {
|
|
2499
|
+
const action = command.slice(5), segments = ["apps", options.positionals[0], action];
|
|
2500
|
+
if (action === "get")
|
|
2501
|
+
return client.flagship(projectId, segments, environment);
|
|
2502
|
+
const revision = requiredFlag(options, "expected-revision");
|
|
2503
|
+
if (!/^\d+$/.test(revision) || !Number.isSafeInteger(Number(revision)))
|
|
2504
|
+
throw new CliError("flagship_revision_invalid", "Use an observed collection revision.");
|
|
2505
|
+
return client.flagship(projectId, segments, environment, { body: { expectedRevision: Number(revision), ...(action === "rename" ? { name: requiredFlag(options, "name") } : {}), ...(action === "delete" || action === "disconnect" ? { confirm: requiredFlag(options, "confirm") } : {}) } });
|
|
2506
|
+
}
|
|
2484
2507
|
const action = command.slice(6), app = options.positionals[0], key = options.positionals[1];
|
|
2485
2508
|
const segments = ["flags", app, ...(key ? [flagKey(key)] : [])];
|
|
2486
2509
|
if (action === "list" || action === "get")
|
|
2487
2510
|
return client.flagship(projectId, segments, environment);
|
|
2488
2511
|
segments.push(action);
|
|
2489
|
-
if (action === "changelog")
|
|
2512
|
+
if (action === "changelog" || action === "export")
|
|
2490
2513
|
return client.flagship(projectId, segments, environment);
|
|
2514
|
+
if (action === "diff")
|
|
2515
|
+
return client.flagship(projectId, segments, environment, { body: { definition: await definitionFile() } });
|
|
2491
2516
|
if (action === "evaluate") {
|
|
2492
|
-
const
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2517
|
+
const file = options.flags.get("context-file");
|
|
2518
|
+
const context = file ? flagContext(JSON.parse(new TextDecoder().decode(await readBoundedFile(resolve(cwd, file), 16 * 1024)))) : {};
|
|
2519
|
+
const type = options.flags.get("type"), fallbackFile = options.flags.get("fallback-file");
|
|
2520
|
+
if (!!type !== !!fallbackFile)
|
|
2521
|
+
throw new CliError("flagship_fallback_type_invalid", "Choose both --type and --fallback-file.");
|
|
2522
|
+
return client.flagship(projectId, segments, environment, { body: { context, ...(type ? { type, fallback: JSON.parse(new TextDecoder().decode(await readBoundedFile(resolve(cwd, fallbackFile), 10000))) } : {}) } });
|
|
2497
2523
|
}
|
|
2498
2524
|
const revision = options.flags.get("expected-revision");
|
|
2499
2525
|
// Read before mutation by default. Server rechecks this revision inside serialization.
|
|
@@ -2508,31 +2534,23 @@ async function flagshipCommand(command, cwd, options, runtime) {
|
|
|
2508
2534
|
else
|
|
2509
2535
|
expectedRevision = (await client.flagship(projectId, segments.slice(0, -1), environment)).revision;
|
|
2510
2536
|
const body = { expectedRevision };
|
|
2511
|
-
if (action === "
|
|
2512
|
-
|
|
2513
|
-
if (
|
|
2514
|
-
throw new CliError("
|
|
2515
|
-
if (
|
|
2516
|
-
body.
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
}
|
|
2525
|
-
body.customerIds = customerIds(value);
|
|
2537
|
+
if (action === "apply" || action === "delete") {
|
|
2538
|
+
// General editing requires a reviewed revision and digest, rather than auto-adopting a new baseline.
|
|
2539
|
+
if (revision === undefined)
|
|
2540
|
+
throw new CliError("flagship_revision_required", "Review flags diff, then supply --expected-revision and --expected-digest.");
|
|
2541
|
+
if (action === "apply")
|
|
2542
|
+
body.definition = await definitionFile();
|
|
2543
|
+
body.expectedDigest = requiredFlag(options, "expected-digest");
|
|
2544
|
+
if (action === "delete") {
|
|
2545
|
+
if (options.flags.get("confirm") !== key)
|
|
2546
|
+
throw new CliError("flagship_delete_confirmation_required", `Use --confirm ${key} after cleanup. ${FLAG_DELETION_GUIDANCE}`);
|
|
2547
|
+
body.confirm = key;
|
|
2548
|
+
if (!options.booleans.has("json"))
|
|
2549
|
+
runtime.stderr.write(`${FLAG_DELETION_GUIDANCE}\n`);
|
|
2526
2550
|
}
|
|
2527
2551
|
}
|
|
2528
|
-
if (action === "
|
|
2529
|
-
|
|
2530
|
-
throw new CliError("flagship_rollout_invalid", "V1 supports --to on --by customerId.");
|
|
2531
|
-
const percentage = requiredFlag(options, "percentage");
|
|
2532
|
-
if (!/^(?:\d+)(?:\.\d{1,2})?$/.test(percentage))
|
|
2533
|
-
throw new CliError("flagship_percentage_invalid", "Use a percentage from 0 to 100 with at most two decimals.");
|
|
2534
|
-
body.percentage = rolloutPercentage(Number(percentage));
|
|
2535
|
-
}
|
|
2552
|
+
if (action === "create")
|
|
2553
|
+
body.definition = createDefinition;
|
|
2536
2554
|
return client.flagship(projectId, segments, environment, { body });
|
|
2537
2555
|
}
|
|
2538
2556
|
function commandTopic(argv) {
|
|
@@ -2552,7 +2570,7 @@ function commandTopic(argv) {
|
|
|
2552
2570
|
const topic = `preview ${argv[1]}`;
|
|
2553
2571
|
return argv.includes("--help") || argv.includes("-h") ? { topic: "help", values: [topic] } : { topic, values: argv.slice(2) };
|
|
2554
2572
|
}
|
|
2555
|
-
if (parent === "email" || parent === "auth" || parent === "delivery" || parent === "source" || parent === "vars" || parent === "skill" || parent === "skills" || parent === "config" || parent === "production" || parent === "workflows") {
|
|
2573
|
+
if (parent === "artifact" || parent === "email" || parent === "auth" || parent === "delivery" || parent === "source" || parent === "vars" || parent === "skill" || parent === "skills" || parent === "config" || parent === "production" || parent === "workflows") {
|
|
2556
2574
|
const child = argv[1];
|
|
2557
2575
|
if (!child || child.startsWith("--"))
|
|
2558
2576
|
return { topic: "help", values: [parent] };
|
|
@@ -2627,14 +2645,14 @@ function parseOptions(values) {
|
|
|
2627
2645
|
function assertAllowedOptions(topic, options) {
|
|
2628
2646
|
if (topic.startsWith("flagship ")) {
|
|
2629
2647
|
const common = ["cwd", "json", "api-url", "token-stdin", "profile", "project", "env"];
|
|
2630
|
-
const extras = { "flags create": ["expected-revision"], "flags enable": ["expected-revision"], "flags disable": ["expected-revision"],
|
|
2631
|
-
"flags
|
|
2648
|
+
const extras = { "apps ensure": ["collection"], "apps rename": ["expected-revision", "name"], "apps delete": ["expected-revision", "confirm"], "apps disconnect": ["expected-revision", "confirm"], "apps reconcile": ["expected-revision"], "flags delete": ["expected-revision", "expected-digest", "confirm"], "flags validate": ["file"], "flags diff": ["file"], "flags apply": ["file", "expected-revision", "expected-digest"], "flags create": ["expected-revision", "file"], "flags enable": ["expected-revision"], "flags disable": ["expected-revision"],
|
|
2649
|
+
"flags evaluate": ["context-file", "type", "fallback-file"], "flags reconcile": ["expected-revision"] };
|
|
2632
2650
|
const sub = topic.slice(9);
|
|
2633
|
-
if (!["apps ensure", "apps list", "flags list", "flags get", "flags
|
|
2651
|
+
if (!["apps ensure", "apps list", "apps get", "apps rename", "apps delete", "apps disconnect", "apps reconcile", "flags list", "flags get", "flags export", "flags delete", "flags validate", "flags diff", "flags apply", "flags create", "flags enable", "flags disable", "flags evaluate", "flags changelog", "flags reconcile"].includes(sub))
|
|
2634
2652
|
throw new CliError("argument_unknown", "Unknown flagship command.");
|
|
2635
2653
|
if ([...options.flags.keys(), ...options.booleans].some(k => ![...common, ...(extras[sub] ?? [])].includes(k)))
|
|
2636
2654
|
throw new CliError("argument_unknown", "Unsupported flagship option.");
|
|
2637
|
-
const length = sub
|
|
2655
|
+
const length = sub === "apps ensure" || sub === "apps list" || sub === "flags validate" ? 0 : sub.startsWith("apps") || sub === "flags list" ? 1 : 2;
|
|
2638
2656
|
if (options.positionals.length !== length)
|
|
2639
2657
|
throw new CliError("argument_required", `Expected ${length} positional arguments for ${topic}.`);
|
|
2640
2658
|
return;
|
|
@@ -2642,6 +2660,7 @@ function assertAllowedOptions(topic, options) {
|
|
|
2642
2660
|
const common = ["cwd", "json"];
|
|
2643
2661
|
const authentication = ["api-url", "token-stdin", "profile"];
|
|
2644
2662
|
const allowed = {
|
|
2663
|
+
"artifact finalize": [...common, "artifact-directory"],
|
|
2645
2664
|
docs: common,
|
|
2646
2665
|
capabilities: [...common, ...authentication, "project"],
|
|
2647
2666
|
"email capabilities": [...common, ...authentication, "tenant", "project"],
|
|
@@ -2881,7 +2900,22 @@ function writeOutput(value, options, runtime) {
|
|
|
2881
2900
|
}
|
|
2882
2901
|
function helpText(topic) {
|
|
2883
2902
|
if (topic === "flagship")
|
|
2884
|
-
return
|
|
2903
|
+
return `Usage: andeo flagship apps ensure|list --env ENV [--collection NAME] [--project ID] [--json]
|
|
2904
|
+
andeo flagship apps get|rename|disconnect|delete|reconcile APP_ID --env ENV [--expected-revision N] [--name NAME] [--confirm EXACT_ID]
|
|
2905
|
+
andeo flagship flags validate --file flag.json --json
|
|
2906
|
+
andeo flagship flags list APP_ID --env ENV
|
|
2907
|
+
andeo flagship flags create APP_ID KEY --env ENV --file flag.json [--expected-revision N]
|
|
2908
|
+
andeo flagship flags get|export|enable|disable|changelog|reconcile APP_ID KEY --env ENV [--expected-revision N]
|
|
2909
|
+
andeo flagship flags diff APP_ID KEY --env ENV --file flag.json --json
|
|
2910
|
+
andeo flagship flags apply APP_ID KEY --env ENV --file flag.json --expected-revision N --expected-digest SHA256 --json
|
|
2911
|
+
andeo flagship flags delete APP_ID KEY --env ENV --expected-revision N --expected-digest SHA256 --confirm KEY
|
|
2912
|
+
andeo flagship flags evaluate APP_ID KEY --env ENV [--context-file context.json] [--type boolean|string|number|json --fallback-file fallback.json]
|
|
2913
|
+
|
|
2914
|
+
All definitions use one pinned native Flagship schema. Create requires a complete disabled definition; apply preserves enabled state. Disable returns the configured default, which may differ from the caller fallback. Targeting and rollouts are edited through full definitions; evaluation context uses --context-file. Management numeric/boolean context transport is unverified and fails explicitly; use native evaluation. Pending operations require reconciliation. No conversion analytics or significance calculation is connected.
|
|
2915
|
+
|
|
2916
|
+
Flag deletion requires a disabled saved flag and exact --confirm KEY after cleanup. ${FLAG_DELETION_GUIDANCE} Andeo does not detect remaining evaluations automatically.`;
|
|
2917
|
+
if (topic === "artifact" || topic === "artifact finalize")
|
|
2918
|
+
return "Usage: andeo artifact finalize --artifact-directory DIR [--json]\n\nFinalize workflow coordinators and native Flagship declarations after producer packaging, retaining the attested source revision. Runs offline in the build checkout.";
|
|
2885
2919
|
if (topic === "email" || topic?.startsWith("email "))
|
|
2886
2920
|
return `Usage: andeo email <capabilities|create|list|cursor|wait|read|messages|retire> [options]
|
|
2887
2921
|
|
|
@@ -3004,6 +3038,8 @@ Account workflow entitlement is required for new starts. Run status is authorita
|
|
|
3004
3038
|
return `Andeo merchant developer CLI ${CLI_VERSION}\n\nCommands:\n docs Read version-matched offline guides\n capabilities Inspect scoped support, enablement and permissions\n init Create a new Shopify account starter\n auth login Create or refresh the default login\n auth create Create or re-authorize a named login\n auth activate Bind a named login to a directory tree\n auth deactivate Remove an exact directory binding\n auth status Validate one login or scoped credential\n auth list List local login profiles and bindings\n auth delete Revoke one named login\n auth logout Revoke the default or every login\n link Link this checkout to one permitted project\n doctor Check developer readiness\n dev Run the project-owned local dev command\n check Run project validation\n build Package and verify a portable artifact\n upload Retain a build for administrator publication\n preview Build and deploy an exact preview\n preview entries List eligible connected entry apps\n preview open Reopen an exact deployed preview\n source status Inspect Andeo-managed Git and recent builds\n source branches List exact managed branches and default policy\n source compare Compare two managed branches\n source connect Connect this linked project to managed Git\n source reconnect Prepare, inspect, complete or cancel a tenant namespace cutover\n source pull Clone or fast-forward the exact connected repository\n source token Issue a short-lived repository credential\n source push Push the exact commit without persisting a token\n source promote Fast-forward the default after successful preview checks\n source history Run guarded, archived source-history migration\n email Create and reuse merchant email identities\n workflows Inspect and control app-defined background runs\n vars Manage saved per-environment runtime variables\n source policy Inspect or change automatic publication\n tail Stream logs for one exact linked project\n delivery status Read preview workflow state\n delivery retry Retry a failed preview workflow\n skill install Install the Andeo agent skill\n skills list Discover authenticated practice skills\n skills get Read an authenticated practice skill\n skills file Read one authenticated skill resource\n config example Print the project configuration contract\n\nThere is intentionally no production publish command.\nRun andeo <command> --help for examples.`;
|
|
3005
3039
|
}
|
|
3006
3040
|
function normalizeError(error) {
|
|
3041
|
+
if (error instanceof FlagCompatibilityError)
|
|
3042
|
+
return { code: error.code, message: error.message, details: { field: error.field, schema: FLAGSHIP_SCHEMA } };
|
|
3007
3043
|
if (error instanceof CliApiError) {
|
|
3008
3044
|
return { code: error.code, message: error.message, ...(error.hint ? { hint: error.hint } : {}), ...(error.requestId ? { requestId: error.requestId } : {}), ...(error.details ? { details: error.details } : {}) };
|
|
3009
3045
|
}
|