@devkong/cli 0.0.67-alpha.21 → 0.0.67-alpha.23
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
|
@@ -60771,25 +60771,25 @@ async function spawnCommandWithArgs(command, commandArgs, logger = null, shell =
|
|
|
60771
60771
|
});
|
|
60772
60772
|
});
|
|
60773
60773
|
}
|
|
60774
|
-
function printError(
|
|
60774
|
+
function printError(label, error) {
|
|
60775
60775
|
if (axios_default.isAxiosError(error)) {
|
|
60776
|
-
console.error(
|
|
60777
|
-
console.error("
|
|
60778
|
-
console.error("
|
|
60779
|
-
console.error("
|
|
60780
|
-
console.error("Request Data:", error.config?.data);
|
|
60776
|
+
console.error("url:", error.config?.url);
|
|
60777
|
+
console.error("method:", error.config?.method);
|
|
60778
|
+
console.error("headers:", error.config?.headers);
|
|
60779
|
+
console.error("request data:", error.config?.data);
|
|
60781
60780
|
if (error.response) {
|
|
60782
|
-
console.error("
|
|
60783
|
-
console.error("
|
|
60784
|
-
console.error("
|
|
60781
|
+
console.error("response status:", error.response.status);
|
|
60782
|
+
console.error("response headers:", error.response.headers);
|
|
60783
|
+
console.error("response data:", error.response.data);
|
|
60785
60784
|
} else if (error.request) {
|
|
60786
|
-
console.error("
|
|
60787
|
-
console.error("
|
|
60785
|
+
console.error("request was made but no response received");
|
|
60786
|
+
console.error("request data:", error.request);
|
|
60788
60787
|
}
|
|
60788
|
+
console.error(label + ":", error.message);
|
|
60789
60789
|
} else if (error instanceof AppError) {
|
|
60790
|
-
console.error(
|
|
60790
|
+
console.error(label + ":", error.message);
|
|
60791
60791
|
} else {
|
|
60792
|
-
console.error(
|
|
60792
|
+
console.error(label + ":", error);
|
|
60793
60793
|
}
|
|
60794
60794
|
}
|
|
60795
60795
|
function escapePathSpaces(sourcePath) {
|
|
@@ -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,
|
|
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.
|
|
63582
|
-
|
|
63583
|
-
|
|
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:
|
|
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
|
|
63747
|
-
|
|
63748
|
-
|
|
63749
|
-
|
|
63750
|
-
).default(
|
|
63751
|
-
|
|
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
|
-
|
|
63820
|
+
weights,
|
|
63757
63821
|
Number(options.timeout)
|
|
63758
63822
|
);
|
|
63759
63823
|
} catch (ex) {
|
package/package.json
CHANGED
|
@@ -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,
|
|
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
|
|
@@ -12,5 +12,5 @@ export declare function resolveProjectPath(fileName: string): string;
|
|
|
12
12
|
export declare function generateShortId(): ShortUUID;
|
|
13
13
|
export declare function spawnCommand(commandText: string, logger?: ILogger | null, shell?: boolean): Promise<string>;
|
|
14
14
|
export declare function spawnCommandWithArgs(command: string, commandArgs: string[], logger?: ILogger | null, shell?: boolean): Promise<string>;
|
|
15
|
-
export declare function printError(
|
|
15
|
+
export declare function printError(label: string, error: unknown): void;
|
|
16
16
|
export declare function escapePathSpaces(sourcePath: string): string;
|
|
@@ -20,7 +20,7 @@ export { applyJqFilterReplacements, applyJqObjectReplacements, applyJqTextReplac
|
|
|
20
20
|
export type { JqArray, JqFilter, JqObject, JqSerializer, JqText, RefactorReplacement, } from "./lib/jq";
|
|
21
21
|
export { generateFakeJsonObject, jsonToHumanReadable, prettyJsonObject, prettyJsonText, safeJsonParse, } from "./lib/json";
|
|
22
22
|
export type { JsonArray, JsonObject, JsonSchemaUrl, JsonText } from "./lib/json";
|
|
23
|
-
export { buildExampleObject, buildJSONSchemaType, createEmptyJsonSchema, getNestedValue, getPropertyType, hasJSONSchemaType, isRequired, updateNestedValue, } from "./lib/jsonSchema";
|
|
23
|
+
export { buildExampleObject, buildJSONSchemaType, createEmptyJsonSchema, getNestedValue, getPropertyType, hasJSONSchemaType, isRequired, normalizeNullableUnions, updateNestedValue, } from "./lib/jsonSchema";
|
|
24
24
|
export { KeyGenerator } from "./lib/keyGenerator";
|
|
25
25
|
export { findLastIndex, insertItem, moveItem, unique } from "./lib/list";
|
|
26
26
|
export { deepMerge, deepMergeAll } from "./lib/merge";
|
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
import { JSONSchema7, JSONSchema7TypeName } from "json-schema";
|
|
2
2
|
import { JsonObject, Optional } from "./types";
|
|
3
|
+
/**
|
|
4
|
+
* Collapses Pydantic's optional idiom `anyOf: [X, { "type": "null" }]` (and the `oneOf` form) down
|
|
5
|
+
* to `X`, recursively, and drops the now-dead `$defs`/`definitions`.
|
|
6
|
+
*
|
|
7
|
+
* Pydantic encodes every optional/nullable field as such a union instead of a nullable type, and
|
|
8
|
+
* `$ref` dereferencing inlines the branch but leaves the union in place. A form that reads
|
|
9
|
+
* `properties`/`type` straight off the node then sees an `anyOf` wrapper with no `properties` and
|
|
10
|
+
* renders nothing. When a single non-null branch remains it is merged with the union's sibling
|
|
11
|
+
* keywords (`default`, `description`, `title`, ...); genuine multi-branch unions keep their non-null
|
|
12
|
+
* branches. Run this right after dereferencing, before the schema reaches the form.
|
|
13
|
+
*/
|
|
14
|
+
export declare function normalizeNullableUnions<T = JSONSchema7>(schema: T): T;
|
|
3
15
|
export declare function hasJSONSchemaType(type: JSONSchema7["type"], name: JSONSchema7TypeName): boolean;
|
|
4
16
|
export declare function getPropertyType(schema: Optional<JSONSchema7> | null): string;
|
|
5
17
|
export declare function isRequired(schema: Optional<JSONSchema7> | null, propertyKey: string): boolean;
|