@nanoflags/cli 0.1.0 → 0.2.0
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 +27 -2
- package/dist/bin.js +72 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @nanoflags/cli
|
|
2
2
|
|
|
3
|
-
Command line for NanoFlags: export a project, evaluate flags locally, run scenario checks in CI,
|
|
3
|
+
Command line for NanoFlags: export a project, create flags, evaluate flags locally, run scenario checks in CI,
|
|
4
4
|
publish, upload flag code references, and remove resolved flags from code.
|
|
5
5
|
|
|
6
6
|
```sh
|
|
@@ -19,7 +19,7 @@ workflow (`.github/workflows/release.yml`, tag `cli-v<version>`).
|
|
|
19
19
|
|
|
20
20
|
| | |
|
|
21
21
|
| --- | --- |
|
|
22
|
-
| `NANOFLAGS_KEY` / `--key` | A **CI key** (`nf_ci_…`: export, scenarios, code references) or an **admin key** (`nf_adm_…`: all of that, plus publish). SDK keys are refused by these endpoints. Prefer the environment variable — arguments are visible in process lists and shell history. |
|
|
22
|
+
| `NANOFLAGS_KEY` / `--key` | A **CI key** (`nf_ci_…`: export, scenarios, code references) or an **admin key** (`nf_adm_…`: all of that, plus `flags create` and publish). SDK keys are refused by these endpoints. Prefer the environment variable — arguments are visible in process lists and shell history. |
|
|
23
23
|
| `NANOFLAGS_BASE_URL` / `--base-url` | Edge URL. Default `https://edge.nanoflags.io`. Must be `https` (plain `http` only for `localhost`). |
|
|
24
24
|
| `NANOFLAGS_SDK_KEY` / `--sdk-key` | Optional, only for `eval --source published`: a server SDK key; reads `/v1/bundle` instead of `/v1/ci/published`. |
|
|
25
25
|
|
|
@@ -50,6 +50,31 @@ The saved file can be passed to other commands with `--export <file>` to work of
|
|
|
50
50
|
Table of the project's flags: key, type, purpose, lifetime (permanent/temporary), client exposure, and
|
|
51
51
|
on/off in the draft of the key's environment.
|
|
52
52
|
|
|
53
|
+
### `nanoflags flags create <key> --type boolean|string|number|json [--default <json>] [--description <text>] [--name <text>] [--json]`
|
|
54
|
+
|
|
55
|
+
`POST /v1/admin/flags` — creates a flag in the key's project. **Admin key only** (a CI key is refused
|
|
56
|
+
before any request). Nothing is published: every environment's **draft** starts with the default value,
|
|
57
|
+
and the flag reaches SDKs only with the next publish.
|
|
58
|
+
|
|
59
|
+
```sh
|
|
60
|
+
nanoflags flags create new-checkout --description "New checkout flow" # boolean, default false
|
|
61
|
+
nanoflags flags create theme --type string --default blue --name "Theme" # bare word = string
|
|
62
|
+
nanoflags flags create search-limit --type number --default 25
|
|
63
|
+
nanoflags flags create ranker --type json --default '{"model":"v2","weights":[1,2]}'
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
- `--type` defaults to `boolean`; `--default` defaults to `false` for boolean flags and is required otherwise.
|
|
67
|
+
- `--default` is JSON and must match the type. For string flags a value that is not JSON is taken as the
|
|
68
|
+
string itself (`--default blue` = `--default '"blue"'`).
|
|
69
|
+
- The key follows the dashboard rules: lowercase letters, digits, `-` and `_`, starting with a letter or digit, at most 63.
|
|
70
|
+
- `--name` defaults to the key. Purpose, owner, review date and client exposure are set in the dashboard.
|
|
71
|
+
- The creation is recorded in the project's activity log with the key's name.
|
|
72
|
+
|
|
73
|
+
| Refusal (exit 1) | Meaning |
|
|
74
|
+
| --- | --- |
|
|
75
|
+
| `CONFLICT` | A flag with this key already exists in the project. |
|
|
76
|
+
| `INVALID_VALUE` | The default does not match the flag type. |
|
|
77
|
+
|
|
53
78
|
### `nanoflags eval <flag> [--context <json>] [--source draft|published] [--fallback <json>] [--environment <key>] [--json]`
|
|
54
79
|
|
|
55
80
|
Evaluates one flag **locally** with the NanoFlags engine — the same code the SDKs and the edge run —
|
package/dist/bin.js
CHANGED
|
@@ -258,6 +258,10 @@ var Api = class {
|
|
|
258
258
|
publish(body) {
|
|
259
259
|
return this.call("POST", "/v1/admin/publish", body);
|
|
260
260
|
}
|
|
261
|
+
/** Creates a flag in the key's project (admin key). Every environment's draft starts with `default`. */
|
|
262
|
+
createFlag(body) {
|
|
263
|
+
return this.call("POST", "/v1/admin/flags", body);
|
|
264
|
+
}
|
|
261
265
|
putCodeRefs(body) {
|
|
262
266
|
return this.call("PUT", "/v1/ci/code-refs", body);
|
|
263
267
|
}
|
|
@@ -276,7 +280,7 @@ function explain(e, key2, what) {
|
|
|
276
280
|
}
|
|
277
281
|
|
|
278
282
|
// src/main.ts
|
|
279
|
-
var VERSION = "0.
|
|
283
|
+
var VERSION = "0.2.0";
|
|
280
284
|
var HELP = `nanoflags ${VERSION} \u2014 NanoFlags command line
|
|
281
285
|
|
|
282
286
|
Usage: nanoflags <command> [options]
|
|
@@ -284,6 +288,8 @@ Usage: nanoflags <command> [options]
|
|
|
284
288
|
Commands
|
|
285
289
|
export [--out <file>] Download flags, segments and drafts as JSON
|
|
286
290
|
flags list [--json] List the project's flags
|
|
291
|
+
flags create <key> --type boolean|string|number|json [--default <json>] [--description <text>] [--name <text>] [--json]
|
|
292
|
+
Create a flag in the key's project (admin key); nothing is published
|
|
287
293
|
eval <flag> [--context <json>] [--source draft|published] [--fallback <json>] [--json]
|
|
288
294
|
Evaluate one flag locally and print value, reason and trace
|
|
289
295
|
scenarios run [--source draft|revision] [--commit <sha>] [--run-url <url>] [--json]
|
|
@@ -318,6 +324,10 @@ var OPTIONS = {
|
|
|
318
324
|
"run-url": { type: "string" },
|
|
319
325
|
json: { type: "boolean" },
|
|
320
326
|
"expected-revision": { type: "string" },
|
|
327
|
+
type: { type: "string" },
|
|
328
|
+
default: { type: "string" },
|
|
329
|
+
description: { type: "string" },
|
|
330
|
+
name: { type: "string" },
|
|
321
331
|
note: { type: "string" },
|
|
322
332
|
repo: { type: "string" },
|
|
323
333
|
"dry-run": { type: "boolean" },
|
|
@@ -413,6 +423,66 @@ async function cmdFlagsList(r) {
|
|
|
413
423
|
`);
|
|
414
424
|
return EXIT.ok;
|
|
415
425
|
}
|
|
426
|
+
var FLAG_KEY = /^[a-z0-9][a-z0-9_-]{0,62}$/;
|
|
427
|
+
var FLAG_TYPES = ["boolean", "string", "number", "json"];
|
|
428
|
+
function parseDefault(type, raw) {
|
|
429
|
+
if (raw === void 0) {
|
|
430
|
+
if (type === "boolean") return false;
|
|
431
|
+
throw usage(`flags create needs --default for a ${type} flag: the value every environment's draft starts with (JSON, e.g. ${type === "string" ? `'"blue"'` : type === "number" ? "0" : "'{}'"}).`);
|
|
432
|
+
}
|
|
433
|
+
let value;
|
|
434
|
+
try {
|
|
435
|
+
value = JSON.parse(raw);
|
|
436
|
+
} catch {
|
|
437
|
+
if (type !== "string") throw usage(`--default must be valid JSON for a ${type} flag, got: ${clip(raw, 80)}`);
|
|
438
|
+
value = raw;
|
|
439
|
+
}
|
|
440
|
+
const ok = type === "boolean" ? typeof value === "boolean" : type === "string" ? typeof value === "string" : type === "number" ? typeof value === "number" && Number.isFinite(value) : true;
|
|
441
|
+
if (!ok) throw usage(`--default must be a ${type} value for a ${type} flag, got: ${clip(raw, 80)}`);
|
|
442
|
+
return value;
|
|
443
|
+
}
|
|
444
|
+
var CREATE_HELP = {
|
|
445
|
+
CONFLICT: "Choose another key, or use the existing flag (`nanoflags flags list`).",
|
|
446
|
+
INVALID_VALUE: "The default does not match the flag type.",
|
|
447
|
+
INVALID: "Check the key, name and description."
|
|
448
|
+
};
|
|
449
|
+
async function cmdFlagsCreate(r) {
|
|
450
|
+
const flagKey = r.args[0];
|
|
451
|
+
if (!flagKey || r.args.length > 1) throw usage("Usage: nanoflags flags create <key> --type boolean|string|number|json [--default <json>] [--description <text>] [--name <text>]");
|
|
452
|
+
if (!FLAG_KEY.test(flagKey)) throw usage(`The flag key must start with a lowercase letter or digit and use only lowercase letters, digits, hyphens and underscores (max 63), got "${clip(flagKey, 80)}".`);
|
|
453
|
+
const type = r.values.type ?? "boolean";
|
|
454
|
+
if (!FLAG_TYPES.includes(type)) throw usage(`--type must be one of ${FLAG_TYPES.join(", ")}, got "${type}".`);
|
|
455
|
+
const value = parseDefault(type, r.values.default);
|
|
456
|
+
const description = r.values.description ?? "";
|
|
457
|
+
if (description.length > 2e3) throw usage("--description is limited to 2000 characters.");
|
|
458
|
+
const name = r.values.name;
|
|
459
|
+
if (name !== void 0 && (name.trim() === "" || name.length > 120)) throw usage("--name must be 1\u2013120 characters.");
|
|
460
|
+
const k = key(r);
|
|
461
|
+
const kind = keyKind(k);
|
|
462
|
+
if (kind !== "admin" && kind !== "unknown") throw usage(`flags create needs an admin key; this is a ${kind} key.`);
|
|
463
|
+
try {
|
|
464
|
+
const res = await api(r).createFlag({ key: flagKey, type, default: value, description, ...name !== void 0 ? { name: name.trim() } : {} });
|
|
465
|
+
if (r.values.json) r.io.out(`${JSON.stringify(res, null, 2)}
|
|
466
|
+
`);
|
|
467
|
+
else {
|
|
468
|
+
r.io.out(`Created ${res.key} (${res.type}, default ${JSON.stringify(res.default ?? value)}).
|
|
469
|
+
`);
|
|
470
|
+
r.io.out(`Every environment's draft now has it${Array.isArray(res.environments) && res.environments.length ? ` (${res.environments.join(", ")})` : ""}. Nothing was published.
|
|
471
|
+
`);
|
|
472
|
+
}
|
|
473
|
+
return EXIT.ok;
|
|
474
|
+
} catch (e) {
|
|
475
|
+
const err = e;
|
|
476
|
+
const help = e instanceof ApiError && typeof err.code === "string" ? CREATE_HELP[err.code] : void 0;
|
|
477
|
+
if (help) {
|
|
478
|
+
r.io.err(`Flag not created: ${err.message ?? err.code} [${err.code}]
|
|
479
|
+
${help}
|
|
480
|
+
`);
|
|
481
|
+
return EXIT.failed;
|
|
482
|
+
}
|
|
483
|
+
throw explain(e, k, "create flags");
|
|
484
|
+
}
|
|
485
|
+
}
|
|
416
486
|
function isBundle(v) {
|
|
417
487
|
if (typeof v !== "object" || v === null) return false;
|
|
418
488
|
const b = v;
|
|
@@ -731,6 +801,7 @@ Run \`nanoflags --help\` for usage.
|
|
|
731
801
|
try {
|
|
732
802
|
if (command === "export") return await cmdExport(run(1));
|
|
733
803
|
if (command === "flags" && sub === "list") return await cmdFlagsList(run(2));
|
|
804
|
+
if (command === "flags" && sub === "create") return await cmdFlagsCreate(run(2));
|
|
734
805
|
if (command === "eval") return await cmdEval(run(1));
|
|
735
806
|
if (command === "scenarios" && sub === "run") return await cmdScenariosRun(run(2));
|
|
736
807
|
if (command === "publish") return await cmdPublish(run(1));
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nanoflags/cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "NanoFlags command line: export, local evaluation, CI scenario checks, publishing, code references and flag cleanup.",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "NanoFlags command line: export, flag creation, local evaluation, CI scenario checks, publishing, code references and flag cleanup.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"nanoflags",
|
|
7
7
|
"feature-flags",
|