@devkong/cli 0.0.67-alpha.2 → 0.0.67-alpha.20

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 CHANGED
@@ -29,14 +29,3 @@ Publish with custom tag to avoid @latest:
29
29
  ```sh
30
30
  npx nx release publish --git-commit=false --tag=dev
31
31
  ```
32
-
33
- ## Publish to the Nexus
34
-
35
- ```shell script
36
- code ~/.npmrc
37
-
38
- @kong:registry=https://nexus.shared.sg.app-dts.net/repository/npm-hosted/
39
- //nexus.shared.sg.app-dts.net/repository/npm-hosted/:username=kong
40
- //nexus.shared.sg.app-dts.net/repository/npm-hosted/:_password={BASE_64_PASSWORD}
41
- //nexus.shared.sg.app-dts.net/repository/npm-hosted/:always-auth=true
42
- ```
@@ -1,28 +1,28 @@
1
- @echo off
2
-
3
- set EXTENSION_DIR=%1
4
- echo %EXTENSION_DIR%
5
-
6
- where python >nul 2>&1
7
- if %errorlevel% equ 0 (
8
- set PYTHON_CMD=python
9
- ) else (
10
- set PYTHON_CMD=python3
11
- )
12
-
13
- where pip >nul 2>&1
14
- if %errorlevel% equ 0 (
15
- set PIP_CMD=pip
16
- ) else (
17
- set PIP_CMD=pip3
18
- )
19
-
20
- cd %EXTENSION_DIR%
21
-
22
- %PYTHON_CMD% -m venv .venv
23
-
24
- call .venv\Scripts\activate.bat
25
-
26
- %PIP_CMD% freeze > requirements-lock.txt
27
- %PIP_CMD% install --default-timeout=120 -r requirements.txt -r requirements-dev.txt
28
- %PIP_CMD% freeze > requirements-lock.txt
1
+ @echo off
2
+
3
+ set EXTENSION_DIR=%1
4
+ echo %EXTENSION_DIR%
5
+
6
+ where python >nul 2>&1
7
+ if %errorlevel% equ 0 (
8
+ set PYTHON_CMD=python
9
+ ) else (
10
+ set PYTHON_CMD=python3
11
+ )
12
+
13
+ cd %EXTENSION_DIR%
14
+
15
+ REM Recreate the venv from scratch so a stale interpreter path (e.g. left over
16
+ REM after a Python upgrade or a moved/copied project) can't leave pip pointing
17
+ REM at a missing binary.
18
+ %PYTHON_CMD% -m venv --clear .venv
19
+ if errorlevel 1 exit /b 1
20
+
21
+ call .venv\Scripts\activate.bat
22
+
23
+ REM Invoke pip via `python -m pip` rather than the pip/pip3 wrapper so we don't
24
+ REM depend on the wrapper script's (possibly stale) shebang.
25
+ python -m pip freeze > requirements-lock.txt
26
+ python -m pip install --default-timeout=120 -r requirements.txt -r requirements-dev.txt
27
+ if errorlevel 1 exit /b 1
28
+ python -m pip freeze > requirements-lock.txt
@@ -1,4 +1,5 @@
1
1
  #!/bin/bash
2
+ set -e
2
3
 
3
4
  EXTENSION_DIR=$1
4
5
 
@@ -8,18 +9,17 @@ else
8
9
  PYTHON_CMD="python3"
9
10
  fi
10
11
 
11
- if [ -x "$(command -v python)" ]; then
12
- PIP_CMD="pip"
13
- else
14
- PIP_CMD="pip3"
15
- fi
16
-
17
12
  cd "$EXTENSION_DIR" || exit
18
13
 
19
- $PYTHON_CMD -m venv .venv
14
+ # Recreate the venv from scratch so a stale interpreter path (e.g. left over
15
+ # after a Python/nvm/pyenv upgrade) can't leave pip pointing at a missing
16
+ # binary, which fails with "cannot execute: required file not found".
17
+ $PYTHON_CMD -m venv --clear .venv
20
18
 
21
19
  source .venv/bin/activate
22
20
 
23
- $PIP_CMD freeze > requirements-lock.txt
24
- $PIP_CMD install --default-timeout=120 -r requirements-dev.txt -r requirements.txt
25
- $PIP_CMD freeze > requirements-lock.txt
21
+ # Invoke pip via `python -m pip` rather than the pip/pip3 wrapper so we don't
22
+ # depend on the wrapper script's (possibly stale) shebang.
23
+ python -m pip freeze > requirements-lock.txt
24
+ python -m pip install --default-timeout=120 -r requirements-dev.txt -r requirements.txt
25
+ python -m pip freeze > requirements-lock.txt
package/index.js CHANGED
@@ -60795,13 +60795,6 @@ function printError(message2, error) {
60795
60795
  function escapePathSpaces(sourcePath) {
60796
60796
  return sourcePath.indexOf(" ") <= 0 ? sourcePath : `"${sourcePath}"`;
60797
60797
  }
60798
- function renderProgressBar(elapsedSeconds, timeoutSeconds, status) {
60799
- const width = 24;
60800
- const ratio = timeoutSeconds > 0 ? Math.min(1, elapsedSeconds / timeoutSeconds) : 0;
60801
- const filled = Math.round(width * ratio);
60802
- const bar = "\u2588".repeat(filled) + "\u2591".repeat(width - filled);
60803
- return `[${bar}] ${elapsedSeconds}s / ${timeoutSeconds}s \xB7 ${status}`;
60804
- }
60805
60798
 
60806
60799
  // packages/kong-cli/src/common/profile.ts
60807
60800
  var KONG_CONFIG_DIR = import_path3.default.join((0, import_os.homedir)(), ".kong");
@@ -60835,7 +60828,7 @@ function getProfile(name) {
60835
60828
  function printProfile(name) {
60836
60829
  const profileData = getProfile(name);
60837
60830
  console.info(
60838
- `Current profile=${name}, baseUrl=${profileData.kongBaseUrl}, username=${profileData.userName}`
60831
+ `profile=${name}, baseUrl=${profileData.kongBaseUrl}, username=${profileData.userName}`
60839
60832
  );
60840
60833
  }
60841
60834
 
@@ -60852,14 +60845,14 @@ var ConfigureCommand = class {
60852
60845
  const { profileName } = await dist_default14.prompt({
60853
60846
  type: "select",
60854
60847
  name: "profileName",
60855
- message: "Select a profile to configure:",
60848
+ message: "select a profile to configure:",
60856
60849
  choices: [...profileNames, "Create a new profile"]
60857
60850
  });
60858
60851
  if (profileName === "Create a new profile") {
60859
60852
  const { newProfileName } = await dist_default14.prompt({
60860
60853
  type: "input",
60861
60854
  name: "newProfileName",
60862
- message: "Enter name for the new profile:",
60855
+ message: "enter name for the new profile:",
60863
60856
  validate: (input) => profileNames.includes(input) ? "Profile already exists!" : true
60864
60857
  });
60865
60858
  return [newProfileName, createEmptyProfile()];
@@ -60870,20 +60863,20 @@ var ConfigureCommand = class {
60870
60863
  const answerUrl = await dist_default14.prompt({
60871
60864
  type: "input",
60872
60865
  name: "kongUrl",
60873
- message: `Enter URL for profile "${name}":`,
60866
+ message: `enter URL for profile "${name}":`,
60874
60867
  default: profile.kongBaseUrl
60875
60868
  });
60876
60869
  const answerUserName = await dist_default14.prompt({
60877
60870
  type: "input",
60878
60871
  name: "userName",
60879
- message: `Enter user name for profile "${name}":`,
60872
+ message: `enter user name for profile "${name}":`,
60880
60873
  default: profile.userName
60881
60874
  });
60882
60875
  const answerPassword = await dist_default14.prompt({
60883
60876
  type: "password",
60884
60877
  name: "password",
60885
60878
  mask: "*",
60886
- message: `Enter password for user "${answerUserName.userName}":`,
60879
+ message: `enter password for user "${answerUserName.userName}":`,
60887
60880
  default: ""
60888
60881
  });
60889
60882
  const updated = {
@@ -60895,7 +60888,7 @@ var ConfigureCommand = class {
60895
60888
  const entry = new import_keyring.Entry(updated.kongBaseUrl, updated.userName);
60896
60889
  entry.setPassword(answerPassword.password);
60897
60890
  }
60898
- console.log(`Configuration for profile "${name}" has been updated`);
60891
+ console.log(`configuration for profile "${name}" has been updated`);
60899
60892
  return updated;
60900
60893
  }
60901
60894
  };
@@ -61079,8 +61072,9 @@ function getKongJson() {
61079
61072
 
61080
61073
  // packages/kong-cli/src/commands/generateCommand.ts
61081
61074
  var GenerateCommand = class {
61082
- async execute(extensionName, sdk, presetVersion) {
61075
+ async execute(extensionName, sdk, presetVersion, template2) {
61083
61076
  const id = generateShortId();
61077
+ const variant = sdk === "python" ? template2 ?? await this.promptTemplate() : "basic";
61084
61078
  validateKongJson({
61085
61079
  id,
61086
61080
  sdk,
@@ -61092,9 +61086,30 @@ var GenerateCommand = class {
61092
61086
  name: extensionName,
61093
61087
  id,
61094
61088
  platform: sdk,
61089
+ // Passed through to the preset generator. Not named `template` on purpose:
61090
+ // create-nx-workspace reserves that option for cloning a GitHub workspace.
61091
+ variant,
61095
61092
  nxCloud: "skip",
61096
- packageManager: "npm"
61093
+ packageManager: "npm",
61094
+ trustThirdPartyPreset: true,
61095
+ analytics: false
61096
+ });
61097
+ }
61098
+ async promptTemplate() {
61099
+ const { template: template2 } = await dist_default14.prompt({
61100
+ type: "select",
61101
+ name: "template",
61102
+ message: "which template would you like to use?",
61103
+ default: "basic",
61104
+ choices: [
61105
+ { name: "basic - a single operation", value: "basic" },
61106
+ {
61107
+ name: "advanced - multiple operations with a secret example",
61108
+ value: "advanced"
61109
+ }
61110
+ ]
61097
61111
  });
61112
+ return template2;
61098
61113
  }
61099
61114
  };
61100
61115
 
@@ -62930,7 +62945,7 @@ var InstallCommand = class {
62930
62945
  async execute(workspaceDirectory) {
62931
62946
  await new Listr([
62932
62947
  {
62933
- title: "Installing dependencies with pip",
62948
+ title: "installing dependencies with pip",
62934
62949
  task: async (_2, task) => {
62935
62950
  const scriptPath = this.scriptPath();
62936
62951
  await spawnCommandWithArgs(
@@ -63101,12 +63116,12 @@ var ManagementClient = class {
63101
63116
  const url2 = joinUrlAndPath(this.baseUrl, "v1/extensions", extensionId, "/aliases");
63102
63117
  return (await axios_default.get(url2, await this.requestConfigProvider())).data;
63103
63118
  }
63104
- async getAuditLog(objectType2, objectId) {
63119
+ async getAuditLog(objectType2, objectId, createdAfter) {
63105
63120
  const url2 = joinUrlAndPath(this.baseUrl, "v1/audit");
63106
63121
  const config = await this.requestConfigProvider();
63107
63122
  const response = await axios_default.get(url2, {
63108
63123
  ...config,
63109
- params: { ...config.params, objectType: objectType2, objectId }
63124
+ params: { ...config.params, objectType: objectType2, objectId, createdAfter }
63110
63125
  });
63111
63126
  return response.data;
63112
63127
  }
@@ -63126,17 +63141,19 @@ var ListAliasesCommand = class {
63126
63141
  await new Listr(
63127
63142
  [
63128
63143
  {
63129
- title: "List extension aliases",
63144
+ title: "list extension aliases",
63130
63145
  task: async (ctx, task) => {
63131
63146
  const kongJson = getKongJson();
63132
63147
  const aliases = await this.managementClient.getExtensionSnapshotAliases(kongJson.id);
63133
63148
  if (aliases.length === 0) {
63134
- task.output = "No aliases available";
63149
+ task.output = "no aliases available";
63135
63150
  } else {
63136
63151
  const result = [];
63137
63152
  for (const alias of aliases) {
63138
63153
  for (const item of alias.use) {
63139
- result.push(`${alias.name}, ${item.snapshot.version}, ${item.balance}%`);
63154
+ result.push(
63155
+ `${alias.name}, ${item.snapshot.version}, ${item.balance ? `${item.balance}%` : "shadow"}`
63156
+ );
63140
63157
  }
63141
63158
  }
63142
63159
  task.output = result.join("\n");
@@ -63172,12 +63189,12 @@ var ListVersionsCommand = class {
63172
63189
  await new Listr(
63173
63190
  [
63174
63191
  {
63175
- title: "List extension snapshot versions",
63192
+ title: "list extension snapshot versions",
63176
63193
  task: async (ctx, task) => {
63177
63194
  const kongJson = getKongJson();
63178
63195
  const versions = await this.managementClient.getExtensionSnapshotVersions(kongJson.id);
63179
63196
  if (versions.length === 0) {
63180
- task.output = "No versions available";
63197
+ task.output = "no versions available";
63181
63198
  } else {
63182
63199
  const result = [];
63183
63200
  for (const ver of versions) {
@@ -63268,7 +63285,7 @@ var PublishVersionCommand = class {
63268
63285
  const kongJson = getKongJson();
63269
63286
  if (kongJson.sdk === "kotlin" && !this.contractPath) {
63270
63287
  yield {
63271
- title: "Build extension",
63288
+ title: "build extension",
63272
63289
  task: async (ctx, task) => {
63273
63290
  const logger = new ListrTaskLogger(task, this.verbose ? 3 /* DEBUG */ : 0 /* INFO */);
63274
63291
  const cmdText = this.getKotlinBuildCmdTask();
@@ -63279,7 +63296,7 @@ var PublishVersionCommand = class {
63279
63296
  };
63280
63297
  }
63281
63298
  yield {
63282
- title: "Generate schema",
63299
+ title: "generate schema",
63283
63300
  task: async (ctx, task) => {
63284
63301
  const logger = new ListrTaskLogger(task, this.verbose ? 3 /* DEBUG */ : 0 /* INFO */);
63285
63302
  if (this.contractPath) {
@@ -63294,13 +63311,13 @@ var PublishVersionCommand = class {
63294
63311
  rendererOptions: this.defaultRendererOptions
63295
63312
  };
63296
63313
  yield {
63297
- title: "Get publish details",
63314
+ title: "get publish details",
63298
63315
  task: async (ctx) => {
63299
63316
  ctx.publishDetails = await this.registryClient.getPublishDetails(appName);
63300
63317
  }
63301
63318
  };
63302
63319
  yield {
63303
- title: "Docker login",
63320
+ title: "docker login",
63304
63321
  task: async (ctx, task) => {
63305
63322
  const publishDetails = ctx.publishDetails;
63306
63323
  const regUrl = this.resolveRegistryUrl(publishDetails.repoName);
@@ -63312,7 +63329,7 @@ var PublishVersionCommand = class {
63312
63329
  rendererOptions: this.defaultRendererOptions
63313
63330
  };
63314
63331
  yield {
63315
- title: "Docker build",
63332
+ title: "docker build",
63316
63333
  task: async (ctx, task) => {
63317
63334
  const publishDetails = ctx.publishDetails;
63318
63335
  const regUrl = this.resolveRegistryUrl(publishDetails.repoName);
@@ -63325,7 +63342,7 @@ var PublishVersionCommand = class {
63325
63342
  rendererOptions: this.defaultRendererOptions
63326
63343
  };
63327
63344
  yield {
63328
- title: "Docker push",
63345
+ title: "docker push",
63329
63346
  task: async (ctx, task) => {
63330
63347
  const cmd = this.dockerPushCmdText(ctx.fullImageName);
63331
63348
  await spawnCommand(
@@ -63336,7 +63353,7 @@ var PublishVersionCommand = class {
63336
63353
  rendererOptions: this.defaultRendererOptions
63337
63354
  };
63338
63355
  yield {
63339
- title: "Save publish details",
63356
+ title: "save publish details",
63340
63357
  task: async (ctx, task) => {
63341
63358
  const publishDetails = ctx.publishDetails;
63342
63359
  let autoGeneratedContract;
@@ -63405,8 +63422,7 @@ var PublishVersionCommand = class {
63405
63422
  });
63406
63423
  await this.managementClient.createExtensionSnapshot(kongJson.id, snapshot);
63407
63424
  task.output = [
63408
- `The version ${publishDetails.imageVersion} has been successfully published!`,
63409
- joinUrlAndPath(this.profile.kongBaseUrl, "/extensions/aliases")
63425
+ `the version ${publishDetails.imageVersion} has been successfully published!`
63410
63426
  ].join("\n");
63411
63427
  },
63412
63428
  rendererOptions: this.defaultRendererOptions
@@ -63430,7 +63446,7 @@ var PublishVersionCommand = class {
63430
63446
  return `${engine} ${escapePathSpaces(mainPyPath)} --schema`;
63431
63447
  }
63432
63448
  getKotlinBuildCmdTask() {
63433
- return `${this.wslPrefix} ./gradlew build -Dquarkus.package.main-class=io.kong.sdk.function.template.QuarkusApp`;
63449
+ return `${this.wslPrefix} ./gradlew build -x test -Dquarkus.package.main-class=io.kong.sdk.function.template.QuarkusApp`;
63434
63450
  }
63435
63451
  getKotlinSchemaCmdTask() {
63436
63452
  return `${this.wslPrefix} java -Dquarkus-profile=local -jar build/quarkus-app/quarkus-run.jar --schema`;
@@ -63439,7 +63455,7 @@ var PublishVersionCommand = class {
63439
63455
 
63440
63456
  // packages/kong-cli/src/common/deployment.ts
63441
63457
  var DEPLOY_SUCCESS_ACTIONS = /* @__PURE__ */ new Set(["deployment completed"]);
63442
- var DEPLOY_FAILURE_ACTIONS = /* @__PURE__ */ new Set(["deployment failed", "save deployment details"]);
63458
+ var DEPLOY_FAILURE_ACTIONS = /* @__PURE__ */ new Set(["deployment failed"]);
63443
63459
  function isFailureEvent(event) {
63444
63460
  return event.eventType === "error" || DEPLOY_FAILURE_ACTIONS.has(event.action);
63445
63461
  }
@@ -63460,24 +63476,42 @@ function analyzeDeployment(events) {
63460
63476
  }
63461
63477
  async function getDeploymentLog(management, query) {
63462
63478
  const [aliasEvents, baseEvents] = await Promise.all([
63463
- management.getAuditLog(query.objectType, `${query.basedOnId}-${query.aliasName}`),
63464
- management.getAuditLog(query.objectType, query.basedOnId)
63479
+ management.getAuditLog(
63480
+ query.objectType,
63481
+ `${query.basedOnId}-${query.aliasName}`,
63482
+ query.createdAfter
63483
+ ),
63484
+ management.getAuditLog(query.objectType, query.basedOnId, query.createdAfter)
63465
63485
  ]);
63466
63486
  return [...aliasEvents, ...baseEvents].sort((a, b) => b.created.localeCompare(a.created));
63467
63487
  }
63488
+ function deploymentErrorMessage(events) {
63489
+ const failure = events.find(isFailureEvent);
63490
+ if (!failure) {
63491
+ return "Deployment failed.";
63492
+ }
63493
+ const data = failure.eventData;
63494
+ const detail = typeof data?.message === "string" ? data.message : typeof data?.error === "string" ? data.error : void 0;
63495
+ return detail ? `${failure.action}: ${detail}` : failure.action;
63496
+ }
63468
63497
  var sleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
63469
63498
  async function pollDeployment(management, input) {
63470
63499
  const interval = input.intervalMs ?? 2e3;
63471
63500
  const startedAt = Date.now();
63472
63501
  const deadline = startedAt + input.timeoutSeconds * 1e3;
63473
63502
  const elapsed = () => Math.min(input.timeoutSeconds, Math.round((Date.now() - startedAt) / 1e3));
63474
- const report = (status) => input.onProgress?.({ status, elapsedSeconds: elapsed(), timeoutSeconds: input.timeoutSeconds });
63503
+ const report = (current) => input.onProgress?.({
63504
+ status: current.status,
63505
+ events: current.events,
63506
+ elapsedSeconds: elapsed(),
63507
+ timeoutSeconds: input.timeoutSeconds
63508
+ });
63475
63509
  let result = analyzeDeployment(await getDeploymentLog(management, input));
63476
- report(result.status);
63510
+ report(result);
63477
63511
  while (!result.terminal && Date.now() < deadline) {
63478
63512
  await sleep(interval);
63479
63513
  result = analyzeDeployment(await getDeploymentLog(management, input));
63480
- report(result.status);
63514
+ report(result);
63481
63515
  }
63482
63516
  return {
63483
63517
  ...result,
@@ -63509,6 +63543,24 @@ function validateName(value) {
63509
63543
  );
63510
63544
  }
63511
63545
  }
63546
+ var DEPLOY_STEPS = [
63547
+ "create deployment",
63548
+ "save deployment details",
63549
+ "deployment completed"
63550
+ ];
63551
+ function createDeploySteps() {
63552
+ return DEPLOY_STEPS.map((action) => {
63553
+ const handlers = {
63554
+ resolve: () => void 0,
63555
+ reject: () => void 0
63556
+ };
63557
+ const done = new Promise((resolve2, reject) => {
63558
+ handlers.resolve = resolve2;
63559
+ handlers.reject = reject;
63560
+ });
63561
+ return { action, done, settle: (error) => error ? handlers.reject(error) : handlers.resolve() };
63562
+ });
63563
+ }
63512
63564
  var SetAliasCommand = class {
63513
63565
  constructor(profile) {
63514
63566
  this.profile = profile;
@@ -63518,10 +63570,12 @@ var SetAliasCommand = class {
63518
63570
  }
63519
63571
  async execute(aliasName, extensionVersion, timeoutSeconds) {
63520
63572
  validateName(aliasName);
63573
+ const startedAt = utcNow(DEFAULT_TIMEZONE);
63574
+ const steps = createDeploySteps();
63521
63575
  await new Listr(
63522
63576
  [
63523
63577
  {
63524
- title: "Get snapshot details",
63578
+ title: "get snapshot details",
63525
63579
  task: async (ctx, task) => {
63526
63580
  ctx.kongJson = getKongJson();
63527
63581
  ctx.snapshot = await this.managementClient.getExtensionSnapshot(
@@ -63534,7 +63588,7 @@ var SetAliasCommand = class {
63534
63588
  }
63535
63589
  },
63536
63590
  {
63537
- title: "Assign extension alias",
63591
+ title: "assign extension alias",
63538
63592
  task: async (ctx, task) => {
63539
63593
  const now = utcNow(DEFAULT_TIMEZONE);
63540
63594
  const aliasUse = {
@@ -63564,45 +63618,12 @@ var SetAliasCommand = class {
63564
63618
  updatedBy: ""
63565
63619
  };
63566
63620
  await this.publicClient.assignExtensionAlias(ctx.kongJson.id, alias);
63567
- task.output = [
63568
- "Please wait a moment for it to appear in the UI",
63569
- joinUrlAndPath(this.profile.kongBaseUrl, "/extensions/aliases")
63570
- ].join("\n");
63621
+ this.pollDeploymentSteps(steps, ctx.kongJson.id, aliasName, startedAt, timeoutSeconds);
63571
63622
  },
63572
63623
  rendererOptions: { persistentOutput: true }
63573
63624
  },
63574
- {
63575
- title: "Wait for deployment",
63576
- task: async (ctx, task) => {
63577
- let status = "pending";
63578
- const startedAt = Date.now();
63579
- const render = () => {
63580
- const elapsed = Math.min(timeoutSeconds, Math.round((Date.now() - startedAt) / 1e3));
63581
- task.output = renderProgressBar(elapsed, timeoutSeconds, status);
63582
- };
63583
- render();
63584
- const ticker = setInterval(render, 1e3);
63585
- try {
63586
- const result = await pollDeployment(this.managementClient, {
63587
- objectType: "EXTENSION_ALIAS",
63588
- basedOnId: ctx.kongJson.id,
63589
- aliasName,
63590
- timeoutSeconds,
63591
- onProgress: (progress) => {
63592
- status = progress.status;
63593
- render();
63594
- }
63595
- });
63596
- if (result.status === "failed") {
63597
- throw new AppError("Deployment failed. Check the alias logs in the UI.");
63598
- }
63599
- task.output = result.timedOut ? `Still deploying after ${result.waitedSeconds}s \u2014 check the UI for completion.` : `Deployment ${result.status} in ${result.waitedSeconds}s.`;
63600
- } finally {
63601
- clearInterval(ticker);
63602
- }
63603
- },
63604
- rendererOptions: { persistentOutput: true }
63605
- }
63625
+ // Known deployment steps as a checklist; each ticks off on its event.
63626
+ ...steps.map((step) => ({ title: step.action, task: () => step.done }))
63606
63627
  ],
63607
63628
  {
63608
63629
  renderer: "default",
@@ -63614,6 +63635,47 @@ var SetAliasCommand = class {
63614
63635
  }
63615
63636
  ).run();
63616
63637
  }
63638
+ /**
63639
+ * Poll the deployment audit log and settle one step per matching event. Runs
63640
+ * detached from the task list; it settles a step as its event arrives and
63641
+ * fails the rest on a failed/timed-out deployment, stopping on the failed or
63642
+ * "deployment completed" event (poll terminal).
63643
+ */
63644
+ pollDeploymentSteps(steps, basedOnId, aliasName, createdAfter, timeoutSeconds) {
63645
+ const pending = new Map(steps.map((step) => [step.action, step]));
63646
+ const seen = /* @__PURE__ */ new Set();
63647
+ void pollDeployment(this.managementClient, {
63648
+ objectType: "EXTENSION_ALIAS",
63649
+ basedOnId,
63650
+ aliasName,
63651
+ createdAfter,
63652
+ timeoutSeconds,
63653
+ onProgress: (progress) => {
63654
+ for (const event of [...progress.events].reverse()) {
63655
+ if (seen.has(event.action)) {
63656
+ continue;
63657
+ }
63658
+ seen.add(event.action);
63659
+ pending.get(event.action)?.settle();
63660
+ }
63661
+ }
63662
+ }).then((result) => {
63663
+ if (result.status === "failed") {
63664
+ const error = new AppError(deploymentErrorMessage(result.events));
63665
+ steps.forEach((step) => step.settle(error));
63666
+ } else if (result.timedOut) {
63667
+ const error = new AppError(
63668
+ `Still deploying after ${result.waitedSeconds}s \u2014 check the UI for completion.`
63669
+ );
63670
+ steps.forEach((step) => step.settle(error));
63671
+ } else {
63672
+ steps.forEach((step) => step.settle());
63673
+ }
63674
+ }).catch((error) => {
63675
+ const wrapped = error instanceof Error ? error : new AppError(String(error));
63676
+ steps.forEach((step) => step.settle(wrapped));
63677
+ });
63678
+ }
63617
63679
  };
63618
63680
 
63619
63681
  // packages/kong-cli/src/common/cli.ts
@@ -63632,10 +63694,15 @@ import_dotenv_expand.default.expand(import_dotenv.default.config({ path: import_
63632
63694
  async function main() {
63633
63695
  const generateCommand = new Command("generate").description("Generate new extension").argument("name", "Extension name").addOption(
63634
63696
  new Option("--sdk <name>", "Target sdk platform for extension").choices(["python", "kotlin"]).default("python")
63697
+ ).addOption(
63698
+ new Option("--template <name>", "Starter template to scaffold (python only)").choices([
63699
+ "basic",
63700
+ "advanced"
63701
+ ])
63635
63702
  ).addOption(new Option("--verbose", "Show full logs during command execution")).action(async (name, options) => {
63636
63703
  try {
63637
63704
  const presetVersion = getPresetVersion();
63638
- await new GenerateCommand().execute(name, options.sdk, presetVersion);
63705
+ await new GenerateCommand().execute(name, options.sdk, presetVersion, options.template);
63639
63706
  } catch (ex) {
63640
63707
  printError("generate command failed", ex);
63641
63708
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devkong/cli",
3
- "version": "0.0.67-alpha.2",
3
+ "version": "0.0.67-alpha.20",
4
4
  "type": "commonjs",
5
5
  "main": "./index.js",
6
6
  "typings": "./index.d.ts",
@@ -1,4 +1,6 @@
1
1
  import { AppExtension } from "@kong/contract";
2
+ export type ExtensionTemplate = "basic" | "advanced";
2
3
  export declare class GenerateCommand {
3
- execute(extensionName: AppExtension["name"], sdk: "kotlin" | "python", presetVersion: string): Promise<void>;
4
+ execute(extensionName: AppExtension["name"], sdk: "kotlin" | "python", presetVersion: string, template?: ExtensionTemplate): Promise<void>;
5
+ private promptTemplate;
4
6
  }
@@ -5,4 +5,11 @@ export declare class SetAliasCommand {
5
5
  private managementClient;
6
6
  constructor(profile: Profile);
7
7
  execute(aliasName: string, extensionVersion: number, timeoutSeconds: number): Promise<void>;
8
+ /**
9
+ * Poll the deployment audit log and settle one step per matching event. Runs
10
+ * detached from the task list; it settles a step as its event arrives and
11
+ * fails the rest on a failed/timed-out deployment, stopping on the failed or
12
+ * "deployment completed" event (poll terminal).
13
+ */
14
+ private pollDeploymentSteps;
8
15
  }
@@ -12,6 +12,8 @@ export interface DeploymentQuery {
12
12
  objectType: DeployObjectType;
13
13
  basedOnId: string;
14
14
  aliasName: string;
15
+ /** Only fetch audit events created at/after this UTC timestamp (ISO-8601). */
16
+ createdAfter?: string;
15
17
  }
16
18
  export interface DeploymentResult {
17
19
  /**
@@ -36,9 +38,17 @@ export declare function analyzeDeployment(events: AppAuditLog[]): DeploymentResu
36
38
  * `basedOnId`, so both object ids are fetched and merged newest-first.
37
39
  */
38
40
  export declare function getDeploymentLog(management: ManagementClient, query: DeploymentQuery): Promise<AppAuditLog[]>;
41
+ /**
42
+ * Best-effort human-readable reason for a failed deployment, pulled from the
43
+ * first failure event's `eventData` (falling back to its action label) so the
44
+ * actual backend error can be surfaced to the console.
45
+ */
46
+ export declare function deploymentErrorMessage(events: AppAuditLog[]): string;
39
47
  export interface PollProgress {
40
48
  /** Latest analyzed deployment status. */
41
49
  status: Loading;
50
+ /** Full deployment audit log analyzed so far, newest first. */
51
+ events: AppAuditLog[];
42
52
  /** Seconds elapsed since polling started (capped at `timeoutSeconds`). */
43
53
  elapsedSeconds: number;
44
54
  /** The configured deploy-wait budget. */
@@ -1,4 +1,4 @@
1
- import { Loading, Optional, ShortUUID } from "@kong/ts";
1
+ import { Optional, ShortUUID } from "@kong/ts";
2
2
  export interface ILogger {
3
3
  info(message: string): void;
4
4
  debug(message: string): void;
@@ -14,5 +14,3 @@ export declare function spawnCommand(commandText: string, logger?: ILogger | nul
14
14
  export declare function spawnCommandWithArgs(command: string, commandArgs: string[], logger?: ILogger | null, shell?: boolean): Promise<string>;
15
15
  export declare function printError(message: string, error: unknown): void;
16
16
  export declare function escapePathSpaces(sourcePath: string): string;
17
- /** Render a fixed-width textual progress bar for live task output. */
18
- export declare function renderProgressBar(elapsedSeconds: number, timeoutSeconds: number, status: Loading): string;
@@ -11,5 +11,5 @@ export declare class ManagementClient {
11
11
  getExtensionSnapshot(extensionId: AppExtension["id"], extensionSnapshotVersion: AppSnapshot["version"]): Promise<Optional<AppSnapshotDetails>>;
12
12
  getExtensionSnapshotVersions(extensionId: AppExtension["id"]): Promise<AppSnapshot[]>;
13
13
  getExtensionSnapshotAliases(extensionId: AppExtension["id"]): Promise<AppExtensionAlias[]>;
14
- getAuditLog(objectType: DeployObjectType, objectId: string): Promise<AppAuditLog[]>;
14
+ getAuditLog(objectType: DeployObjectType, objectId: string, createdAfter?: string): Promise<AppAuditLog[]>;
15
15
  }
@@ -1,7 +1,7 @@
1
1
  export { makeServerlessJavaAppProps, serverlessFlowBuilder, stringifyServerlessFlow, } from "./lib/kongServerless";
2
2
  export type { FloweyServerlessBuilder } from "./lib/kongServerless";
3
3
  export type { KongSpecJobSession } from "./lib/kongSession";
4
- export type { KongSpecAction, KongSpecActionCallFunction, KongSpecActionCallProcess, KongSpecActionDelay, KongSpecActionInject, KongSpecActionSendEvent, KongSpecAnnotation, KongSpecBranch, KongSpecCache, KongSpecCallModel, KongSpecCircuitBreaker, KongSpecCondition, KongSpecEffect, KongSpecEffectStatus, KongSpecError, KongSpecEventBroker, KongSpecEventConnection, KongSpecFeatureCompensation, KongSpecFlow, KongSpecFunctionRef, KongSpecHowToInvoke, KongSpecHowToOutput, KongSpecHowToStore, KongSpecOutputAppend, KongSpecProcessRef, KongSpecRecord, KongSpecRecordHeader, KongSpecRetry, KongSpecRule, KongSpecSecretRef, KongSpecSecretType, KongSpecState, KongSpecStateCallFunction, KongSpecStateCallProcess, KongSpecStateCompensate, KongSpecStateDelay, KongSpecStateExit, KongSpecStateFail, KongSpecStateForeach, KongSpecStateInject, KongSpecStateParallel, KongSpecStateResult, KongSpecStateRule, KongSpecStateSendEvent, KongSpecStateStart, KongSpecStateSucceed, KongSpecStateSwitch, KongSpecStateWaitEvent, KongSpecStoreRetryControl, KongSpecSupportsActions, KongSpecSupportsErrors, KongSpecSupportsInvoke, KongSpecSupportsOutputSample, KongSpecSupportsOutputStore, KongSpecSupportsTransition, } from "./lib/kongSpec";
4
+ export type { KongSpecAction, KongSpecActionCallFunction, KongSpecActionCallProcess, KongSpecActionDelay, KongSpecActionInject, KongSpecActionSendEvent, KongSpecAnnotation, KongSpecBranch, KongSpecCache, KongSpecCallModel, KongSpecCircuitBreaker, KongSpecCondition, KongSpecEffect, KongSpecEffectStatus, KongSpecError, KongSpecEventBroker, KongSpecEventConnection, KongSpecFeatureCompensation, KongSpecFlow, KongSpecFunctionRef, KongSpecHowToInvoke, KongSpecHowToOutput, KongSpecHowToStore, KongSpecOutputAppend, KongSpecProcessRef, KongSpecRecord, KongSpecRecordHeader, KongSpecRetry, KongSpecRule, KongSpecSecretRef, KongSpecSecretType, KongSpecState, KongSpecStateCallFunction, KongSpecStateCallProcess, KongSpecStateCompensate, KongSpecStateDelay, KongSpecStateExit, KongSpecStateFail, KongSpecStateForeach, KongSpecStateInject, KongSpecStateParallel, KongSpecStateResult, KongSpecStateRule, KongSpecStateSendEvent, KongSpecStateStart, KongSpecStateSucceed, KongSpecStateSwitch, KongSpecStateWaitEvent, KongSpecStoreRetryControl, KongSpecSupportsActions, KongSpecSupportsErrors, KongSpecSupportsInvoke, KongSpecSupportsOutputSample, KongSpecSupportsOutputStore, KongSpecSupportsTaskMode, KongSpecSupportsTransition, } from "./lib/kongSpec";
5
5
  export { KongSpecBuild } from "./lib/kongSpecBuild";
6
6
  export { KongSpecDoc } from "./lib/kongSpecDoc";
7
7
  export { KongSpecEnv } from "./lib/kongSpecEnv";
@@ -14,5 +14,7 @@ export { KongSpecRef } from "./lib/kongSpecRef";
14
14
  export type { KongSpecActionPath, KongSpecStatePath } from "./lib/kongSpecRef";
15
15
  export { convertSpecToServerless } from "./lib/kongSpecToServerless";
16
16
  export { findSourceState, getActions, getConnectedExitStates, getDependencies, outputAggregator, outputAggregatorUnsafe, parallelStateOutputAggregator, parallelStateOutputAggregatorUnsafe, quoteSecretReferences, traverseFlowUp, } from "./lib/kongSpecUtils";
17
+ export { getKongFunctionOperations, getKongFunctionOperationSchema, getKongSpecXUI, getKongSpecXUIComponentConfig, isKongFunctionContractSchema, } from "./lib/kongSpecFunctionContract";
18
+ export type { KongSpecXUIComponentConfig, KongSpecXUIConfig, KongSpecXUIFormConfig, KongSpecXUISecretConfig, } from "./lib/kongSpecFunctionContract";
17
19
  export { validate } from "./lib/kongSpecValidate";
18
20
  export type { KongSpecValidationIssue, KongSpecValidators } from "./lib/kongSpecValidate";
@@ -106,7 +106,10 @@ export interface KongSpecSupportsDataGuards {
106
106
  export interface KongSpecSupportsOutputSample {
107
107
  outputSample: Optional<JsonText>;
108
108
  }
109
- export interface KongSpecCallModel extends KongSpecSupportsInput, KongSpecSupportsOutputFilter, KongSpecSupportsOutputStore, KongSpecSupportsOutputAppend, KongSpecSupportsOutputSample, KongSpecSupportsDataGuards, KongSpecSupportsInvoke {
109
+ export interface KongSpecSupportsTaskMode {
110
+ mode: "blocking" | "non-blocking";
111
+ }
112
+ export interface KongSpecCallModel extends KongSpecSupportsInput, KongSpecSupportsOutputFilter, KongSpecSupportsOutputStore, KongSpecSupportsOutputAppend, KongSpecSupportsOutputSample, KongSpecSupportsDataGuards, KongSpecSupportsTaskMode, KongSpecSupportsInvoke {
110
113
  }
111
114
  export interface KongSpecSupportsErrors {
112
115
  errors: KongSpecError[];
@@ -15,6 +15,7 @@ export declare class KongSpecDoc {
15
15
  static aboutIdentify: string;
16
16
  static aboutSizeLimit: (bytes: string) => string;
17
17
  static aboutCallFunction: string;
18
+ static aboutMode: string;
18
19
  static aboutCallProcess: string;
19
20
  static aboutCompensate: string;
20
21
  static aboutFail: string;
@@ -0,0 +1,26 @@
1
+ import { Optional } from "@kong/ts";
2
+ import { JSONSchema7 } from "json-schema";
3
+ import { KongSpecSecretType } from "./kongSpec";
4
+ export interface KongSpecXUISecretConfig {
5
+ purpose: string;
6
+ types: Array<KongSpecSecretType>;
7
+ }
8
+ export interface KongSpecXUIComponentConfig {
9
+ type: "TEXT_AREA";
10
+ }
11
+ export type KongSpecXUIFormConfig = Record<string, KongSpecXUIComponentConfig>;
12
+ export interface KongSpecXUIConfig {
13
+ version?: number;
14
+ secrets: Array<KongSpecXUISecretConfig>;
15
+ form: KongSpecXUIFormConfig;
16
+ }
17
+ /** Reads the `x-ui` block of a schema, if present. */
18
+ export declare function getKongSpecXUI(schema: Optional<JSONSchema7> | null): Optional<KongSpecXUIConfig>;
19
+ /** Reads the `x-ui.form` component config for a property path (e.g. `[".notes"]`). */
20
+ export declare function getKongSpecXUIComponentConfig(schema: Optional<JSONSchema7> | null, path: string[]): Optional<KongSpecXUIComponentConfig>;
21
+ /** True when the schema describes a KongFunctionContract (i.e. it exposes a `request` envelope). */
22
+ export declare function isKongFunctionContractSchema(schema: Optional<JSONSchema7> | null): boolean;
23
+ /** The operation names of a contract schema — all object properties except `request`. */
24
+ export declare function getKongFunctionOperations(schema: Optional<JSONSchema7> | null): string[];
25
+ /** The sub-schema describing the payload of a single operation. */
26
+ export declare function getKongFunctionOperationSchema(schema: Optional<JSONSchema7> | null, operation: string): Optional<JSONSchema7>;
@@ -8,7 +8,7 @@ interface TestItem {
8
8
  interface Dependency {
9
9
  type: "function" | "process";
10
10
  tenant: SessionTenant;
11
- basedOn: KongSpecFunctionRef["basedOn"] | KongSpecFunctionRef["basedOn"];
11
+ basedOn: KongSpecFunctionRef["basedOn"] | KongSpecProcessRef["basedOn"];
12
12
  aliasName: KongSpecFunctionRef["aliasName"] | KongSpecProcessRef["aliasName"];
13
13
  usage: Array<KongSpecAction["name"] | KongSpecState["name"]>;
14
14
  }
@@ -1,5 +1,5 @@
1
1
  import { AppSnapshot } from "./appSnapshot";
2
2
  export interface AppAliasUse {
3
3
  snapshot: AppSnapshot;
4
- balance: number;
4
+ balance: number | null;
5
5
  }
@@ -3,7 +3,7 @@ import { AppAliasUse } from "./appAliasUse";
3
3
  import { AppSnapshotDetails } from "./appSnapshotDetails";
4
4
  export interface AppAliasUseDetails extends AppAliasUse {
5
5
  snapshot: AppSnapshotDetails;
6
- balance: number;
6
+ balance: number | null;
7
7
  inputFilter?: JqFilter;
8
8
  outputFilter?: JqFilter;
9
9
  }