@devkong/cli 0.0.67-alpha.22 → 0.0.67-alpha.24

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/index.js CHANGED
@@ -63543,6 +63543,63 @@ function validateName(value) {
63543
63543
  );
63544
63544
  }
63545
63545
  }
63546
+ var MAX_USES = 5;
63547
+ function parseVersionWeight(text) {
63548
+ const [versionPart, weightPart, ...rest] = text.split(":");
63549
+ if (rest.length > 0) {
63550
+ throw new AppError(
63551
+ `Invalid version config "${text}". Expected "<version>" or "<version>:<weight|shadow>".`
63552
+ );
63553
+ }
63554
+ if (!/^\d+$/.test(versionPart)) {
63555
+ throw new AppError(
63556
+ `Invalid version "${versionPart}" in "${text}". Expected a positive integer.`
63557
+ );
63558
+ }
63559
+ const version = Number(versionPart);
63560
+ if (weightPart === void 0) {
63561
+ return { version, balance: 100 };
63562
+ }
63563
+ if (weightPart.toLowerCase() === "shadow") {
63564
+ return { version, balance: null };
63565
+ }
63566
+ if (!/^\d+$/.test(weightPart)) {
63567
+ throw new AppError(
63568
+ `Invalid weight "${weightPart}" in "${text}". Expected a number 1-100 or "shadow".`
63569
+ );
63570
+ }
63571
+ return { version, balance: Number(weightPart) };
63572
+ }
63573
+ function parseVersionWeightList(list) {
63574
+ if (list.length === 0) {
63575
+ throw new AppError("At least one extension version must be provided.");
63576
+ }
63577
+ if (list.length > MAX_USES) {
63578
+ throw new AppError(`A maximum of ${MAX_USES} version(s) can share an alias.`);
63579
+ }
63580
+ const uses = list.map(parseVersionWeight);
63581
+ const seen = /* @__PURE__ */ new Set();
63582
+ for (const use of uses) {
63583
+ if (seen.has(use.version)) {
63584
+ throw new AppError(`Version ${use.version} is listed more than once.`);
63585
+ }
63586
+ seen.add(use.version);
63587
+ }
63588
+ const live = uses.filter((use) => use.balance !== null);
63589
+ if (live.length === 0) {
63590
+ throw new AppError("At least one non-shadow version with live traffic is required.");
63591
+ }
63592
+ if (live.some((use) => use.balance <= 0)) {
63593
+ throw new AppError("All traffic weights must be greater than 0%.");
63594
+ }
63595
+ const total = live.reduce((sum, use) => sum + use.balance, 0);
63596
+ if (total !== 100) {
63597
+ throw new AppError(
63598
+ `Traffic weights must sum to 100% (got ${total}%). ${total < 100 ? "Add" : "Remove"} ${Math.abs(100 - total)}%.`
63599
+ );
63600
+ }
63601
+ return uses;
63602
+ }
63546
63603
  var DEPLOY_STEPS = [
63547
63604
  "create deployment",
63548
63605
  "save deployment details",
@@ -63568,7 +63625,7 @@ var SetAliasCommand = class {
63568
63625
  this.publicClient = new PublicClient(profile.kongBaseUrl, clientHeaderProvider);
63569
63626
  this.managementClient = new ManagementClient(profile.kongBaseUrl, clientHeaderProvider);
63570
63627
  }
63571
- async execute(aliasName, extensionVersion, timeoutSeconds) {
63628
+ async execute(aliasName, weights, timeoutSeconds) {
63572
63629
  validateName(aliasName);
63573
63630
  const startedAt = utcNow(DEFAULT_TIMEZONE);
63574
63631
  const steps = createDeploySteps();
@@ -63578,25 +63635,29 @@ var SetAliasCommand = class {
63578
63635
  title: "get snapshot details",
63579
63636
  task: async (ctx, task) => {
63580
63637
  ctx.kongJson = getKongJson();
63581
- ctx.snapshot = await this.managementClient.getExtensionSnapshot(
63582
- ctx.kongJson.id,
63583
- extensionVersion
63638
+ ctx.aliasUseList = await Promise.all(
63639
+ weights.map(async (use) => {
63640
+ const snapshot = await this.managementClient.getExtensionSnapshot(
63641
+ ctx.kongJson.id,
63642
+ use.version
63643
+ );
63644
+ if (!snapshot || !snapshot.id) {
63645
+ throw new AppError(`Snapshot for version ${use.version} not found`);
63646
+ }
63647
+ return {
63648
+ snapshot,
63649
+ balance: use.balance,
63650
+ inputFilter: void 0,
63651
+ outputFilter: void 0
63652
+ };
63653
+ })
63584
63654
  );
63585
- if (!ctx.snapshot || !ctx.snapshot.id) {
63586
- throw new AppError(`Snapshot for version ${extensionVersion} not found`);
63587
- }
63588
63655
  }
63589
63656
  },
63590
63657
  {
63591
63658
  title: "assign extension alias",
63592
63659
  task: async (ctx, task) => {
63593
63660
  const now = utcNow(DEFAULT_TIMEZONE);
63594
- const aliasUse = {
63595
- snapshot: ctx.snapshot,
63596
- balance: 100,
63597
- inputFilter: void 0,
63598
- outputFilter: void 0
63599
- };
63600
63661
  const alias = {
63601
63662
  name: aliasName,
63602
63663
  basedOnName: ctx.kongJson.name,
@@ -63608,7 +63669,7 @@ var SetAliasCommand = class {
63608
63669
  triggerSettings: {
63609
63670
  targetKey: `/extensions/${ctx.kongJson.id}/aliases/${aliasName}`
63610
63671
  },
63611
- use: [aliasUse],
63672
+ use: ctx.aliasUseList,
63612
63673
  inputSchema: createEmptyJsonSchema(),
63613
63674
  outputSchema: createEmptyJsonSchema(),
63614
63675
  invoke: void 0,
@@ -63743,17 +63804,20 @@ async function main() {
63743
63804
  printError("list-versions command failed", ex);
63744
63805
  }
63745
63806
  });
63746
- const setAliasCommand = new Command("set-alias").description("Assign alias to an extension").argument("alias-name", "Alias name to assign").argument("extension-version", "Extension version to use. Should be one of existing versions").addOption(new Option("--profile <name>", "Configured Kong profile to use").default("default")).addOption(
63747
- new Option(
63748
- "--timeout <seconds>",
63749
- "How long to wait for the deployment to finish"
63750
- ).default("120")
63751
- ).action(async (aliasName, extensionVersion, options) => {
63807
+ const setAliasCommand = new Command("set-alias").description("Assign alias to one or more extension versions").argument("<alias-name>", "Alias name to assign").argument(
63808
+ "<version-spec...>",
63809
+ "Version assignments. Each is '<version>', '<version>:<weight>', or '<version>:shadow'. Simple form 'set-alias stable 1' sends 100% of traffic to version 1. Advanced form 'set-alias stable 1:80 2:20 3:shadow' load-balances live traffic 80/20 across versions 1 and 2 (weights must sum to 100%) while mirroring it to shadow version 3."
63810
+ ).addOption(new Option("--profile <name>", "Configured Kong profile to use").default("default")).addOption(
63811
+ new Option("--timeout <seconds>", "How long to wait for the deployment to finish").default(
63812
+ "120"
63813
+ )
63814
+ ).action(async (aliasName, versionSpecs, options) => {
63752
63815
  try {
63816
+ const weights = parseVersionWeightList(versionSpecs);
63753
63817
  printProfile(options.profile);
63754
63818
  await new SetAliasCommand(getProfile(options.profile)).execute(
63755
63819
  aliasName,
63756
- extensionVersion,
63820
+ weights,
63757
63821
  Number(options.timeout)
63758
63822
  );
63759
63823
  } catch (ex) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devkong/cli",
3
- "version": "0.0.67-alpha.22",
3
+ "version": "0.0.67-alpha.24",
4
4
  "type": "commonjs",
5
5
  "main": "./index.js",
6
6
  "typings": "./index.d.ts",
@@ -1,10 +1,22 @@
1
1
  import { Profile } from "../common/profile";
2
+ /** A single version→weight assignment parsed from the command line. */
3
+ export interface AppAliasVersionWeight {
4
+ version: number;
5
+ /** Traffic percentage, or `null` for a "shadow" version (mirrored, no live traffic). */
6
+ balance: number | null;
7
+ }
8
+ /**
9
+ * Parse and validate all version configs. Live (non-shadow) weights must be > 0 and
10
+ * sum to exactly 100%; shadow versions are excluded from that total. Versions may
11
+ * not repeat and no more than {@link MAX_USES} versions can share an alias.
12
+ */
13
+ export declare function parseVersionWeightList(list: string[]): AppAliasVersionWeight[];
2
14
  export declare class SetAliasCommand {
3
15
  private profile;
4
16
  private publicClient;
5
17
  private managementClient;
6
18
  constructor(profile: Profile);
7
- execute(aliasName: string, extensionVersion: number, timeoutSeconds: number): Promise<void>;
19
+ execute(aliasName: string, weights: AppAliasVersionWeight[], timeoutSeconds: number): Promise<void>;
8
20
  /**
9
21
  * Poll the deployment audit log and settle one step per matching event. Runs
10
22
  * detached from the task list; it settles a step as its event arrives and