@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.
Files changed (3) hide show
  1. package/README.md +27 -2
  2. package/dist/bin.js +72 -1
  3. 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.1.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.1.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",