kanban-lite 1.2.2 → 1.2.3

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/dist/cli.js CHANGED
@@ -173,11 +173,83 @@ function migrateConfigV1ToV2(raw) {
173
173
  }
174
174
  return v2;
175
175
  }
176
+ function loadDotEnv(dir) {
177
+ const envPath = path.join(dir, ".env");
178
+ let content;
179
+ try {
180
+ content = fs.readFileSync(envPath, "utf-8");
181
+ } catch {
182
+ return;
183
+ }
184
+ for (const line of content.split("\n")) {
185
+ const trimmed = line.trim();
186
+ if (!trimmed || trimmed.startsWith("#"))
187
+ continue;
188
+ const eqIdx = trimmed.indexOf("=");
189
+ if (eqIdx < 1)
190
+ continue;
191
+ const key = trimmed.slice(0, eqIdx).trim();
192
+ let val = trimmed.slice(eqIdx + 1).trim();
193
+ if (val.startsWith('"') && val.endsWith('"') || val.startsWith("'") && val.endsWith("'")) {
194
+ val = val.slice(1, -1);
195
+ }
196
+ if (process.env[key] === void 0) {
197
+ process.env[key] = val;
198
+ }
199
+ }
200
+ }
201
+ function resolveConfigEnvVars(node, configFileName, nodePath = "") {
202
+ if (typeof node === "string") {
203
+ return node.replace(/\$\{([^}]+)\}/g, (_match, varName) => {
204
+ const envValue = process.env[varName];
205
+ if (envValue === void 0) {
206
+ throw new Error(
207
+ `missing ${varName} in ${configFileName}: ${nodePath} "${node}"`
208
+ );
209
+ }
210
+ return envValue;
211
+ });
212
+ }
213
+ if (Array.isArray(node)) {
214
+ for (let i = 0; i < node.length; i++) {
215
+ node[i] = resolveConfigEnvVars(node[i], configFileName, `${nodePath}[${i}]`);
216
+ }
217
+ return node;
218
+ }
219
+ if (node !== null && typeof node === "object") {
220
+ const obj = node;
221
+ for (const key of Object.keys(obj)) {
222
+ const jsonKey = /[^a-zA-Z0-9_]/.test(key) ? `"${key}"` : key;
223
+ const childPath = nodePath ? `${nodePath}.${jsonKey}` : `.${jsonKey}`;
224
+ obj[key] = resolveConfigEnvVars(obj[key], configFileName, childPath);
225
+ }
226
+ return obj;
227
+ }
228
+ return node;
229
+ }
176
230
  function readConfig(workspaceRoot) {
177
231
  const filePath = configPath(workspaceRoot);
178
232
  const defaults = { ...DEFAULT_CONFIG, boards: { default: { ...DEFAULT_BOARD_CONFIG, columns: [...DEFAULT_COLUMNS] } } };
233
+ let raw;
234
+ try {
235
+ raw = JSON.parse(fs.readFileSync(filePath, "utf-8"));
236
+ } catch {
237
+ return defaults;
238
+ }
239
+ loadDotEnv(workspaceRoot);
240
+ try {
241
+ resolveConfigEnvVars(raw, CONFIG_FILENAME);
242
+ } catch (err) {
243
+ const msg = err instanceof Error ? err.message : String(err);
244
+ process.stderr.write(`
245
+ Configuration error: ${msg}
246
+
247
+ Set the missing environment variable before starting the server.
248
+
249
+ `);
250
+ process.exit(1);
251
+ }
179
252
  try {
180
- const raw = JSON.parse(fs.readFileSync(filePath, "utf-8"));
181
253
  const isV1 = raw.version === 1 || !raw.version && !(typeof raw.boards === "object" && raw.boards !== null && !Array.isArray(raw.boards));
182
254
  if (isV1) {
183
255
  const v2 = migrateConfigV1ToV2(raw);
@@ -5315,7 +5387,7 @@ function resolveAuthIdentityPlugin(ref) {
5315
5387
  if (ref.provider === "rbac")
5316
5388
  return RBAC_IDENTITY_PLUGIN;
5317
5389
  const packageName = AUTH_PROVIDER_ALIASES.get(ref.provider) ?? ref.provider;
5318
- return loadExternalAuthIdentityPlugin(packageName, ref.provider);
5390
+ return loadExternalAuthIdentityPlugin(packageName, ref.provider, ref.options);
5319
5391
  }
5320
5392
  function resolveAuthPolicyPlugin(ref) {
5321
5393
  if (ref.provider === "noop")
@@ -5447,8 +5519,13 @@ function selectAuthPolicyPlugin(mod, providerId) {
5447
5519
  return direct;
5448
5520
  return null;
5449
5521
  }
5450
- function loadExternalAuthIdentityPlugin(packageName, providerId) {
5522
+ function loadExternalAuthIdentityPlugin(packageName, providerId, options) {
5451
5523
  const mod = loadExternalModule(packageName);
5524
+ if (options !== void 0 && typeof mod.createAuthIdentityPlugin === "function") {
5525
+ const created = mod.createAuthIdentityPlugin(options);
5526
+ if (isValidAuthIdentityPlugin(created, providerId))
5527
+ return created;
5528
+ }
5452
5529
  const plugin = selectAuthIdentityPlugin(mod, providerId);
5453
5530
  if (!plugin) {
5454
5531
  throw new Error(
@@ -24633,29 +24710,10 @@ async function updateCard(ctx, { cardId, updates, boardId }) {
24633
24710
  return card;
24634
24711
  }
24635
24712
  async function triggerAction(ctx, { cardId, action, boardId }) {
24636
- const config3 = readConfig(ctx.workspaceRoot);
24637
- const { actionWebhookUrl } = config3;
24638
- if (!actionWebhookUrl) {
24639
- throw new Error("No action webhook URL configured. Set actionWebhookUrl in .kanban.json");
24640
- }
24641
24713
  const card = await getCard(ctx, { cardId, boardId });
24642
24714
  if (!card)
24643
24715
  throw new Error(`Card not found: ${cardId}`);
24644
24716
  const resolvedBoardId = card.boardId || ctx._resolveBoardId(boardId);
24645
- const payload = {
24646
- action,
24647
- board: resolvedBoardId,
24648
- list: card.status,
24649
- card: sanitizeCard(card)
24650
- };
24651
- const response = await fetch(actionWebhookUrl, {
24652
- method: "POST",
24653
- headers: { "Content-Type": "application/json" },
24654
- body: JSON.stringify(payload)
24655
- });
24656
- if (!response.ok) {
24657
- throw new Error(`Action webhook responded with ${response.status}: ${response.statusText}`);
24658
- }
24659
24717
  await appendActivityLog(ctx, {
24660
24718
  cardId,
24661
24719
  boardId: resolvedBoardId,
@@ -24666,6 +24724,12 @@ async function triggerAction(ctx, { cardId, action, boardId }) {
24666
24724
  }
24667
24725
  }).catch(() => {
24668
24726
  });
24727
+ return {
24728
+ action,
24729
+ board: resolvedBoardId,
24730
+ list: card.status,
24731
+ card: sanitizeCard(card)
24732
+ };
24669
24733
  }
24670
24734
  async function submitForm(ctx, input) {
24671
24735
  const card = await getCard(ctx, { cardId: input.cardId, boardId: input.boardId });
@@ -26636,21 +26700,17 @@ var init_KanbanSDK = __esm({
26636
26700
  return result;
26637
26701
  }
26638
26702
  /**
26639
- * Triggers a named action for a card by POSTing to the global `actionWebhookUrl`
26640
- * configured in `.kanban.json`.
26703
+ * Triggers a named action for a card.
26641
26704
  *
26642
- * The payload sent to the webhook is:
26643
- * ```json
26644
- * { "action": "retry", "board": "default", "list": "in-progress", "card": { ...sanitizedCard } }
26645
- * ```
26705
+ * Validates the card, appends an activity log entry, and emits the
26706
+ * `card.action.triggered` after-event so registered webhooks receive
26707
+ * the action payload automatically.
26646
26708
  *
26647
26709
  * @param cardId - The ID of the card to trigger the action for.
26648
26710
  * @param action - The action name string (e.g. `'retry'`, `'sendEmail'`).
26649
26711
  * @param boardId - Optional board ID. Defaults to the workspace's default board.
26650
- * @returns A promise resolving when the webhook responds with 2xx.
26651
- * @throws {Error} If no `actionWebhookUrl` is configured in `.kanban.json`.
26712
+ * @returns A promise that resolves when the action has been processed.
26652
26713
  * @throws {Error} If the card is not found.
26653
- * @throws {Error} If the webhook responds with a non-2xx status.
26654
26714
  *
26655
26715
  * @example
26656
26716
  * ```ts
@@ -26660,7 +26720,8 @@ var init_KanbanSDK = __esm({
26660
26720
  */
26661
26721
  async triggerAction(cardId, action, boardId) {
26662
26722
  const mergedInput = await this._runBeforeEvent("card.action.trigger", { cardId, action, boardId }, void 0, boardId);
26663
- return triggerAction(this, mergedInput);
26723
+ const payload = await triggerAction(this, mergedInput);
26724
+ this._runAfterEvent("card.action.triggered", payload, void 0, payload.board);
26664
26725
  }
26665
26726
  /**
26666
26727
  * Moves a card to a different status column and/or position within that column.
@@ -60340,7 +60401,7 @@ async function main() {
60340
60401
  );
60341
60402
  server.tool(
60342
60403
  "trigger_action",
60343
- "Trigger a named action on a card. The action name must match one of the card's configured actions. Calls the configured action webhook URL with the action name and card details.",
60404
+ "Trigger a named action on a card. The action name must match one of the card's configured actions. Emits a card.action.triggered event delivered to registered webhooks.",
60344
60405
  {
60345
60406
  card_id: external_exports4.string().describe("Card ID (partial match supported)"),
60346
60407
  action: external_exports4.string().describe("Action name to trigger"),
@@ -66751,7 +66812,7 @@ var require_thread_stream = __commonJS({
66751
66812
  var { version: version3 } = require_package();
66752
66813
  var { EventEmitter: EventEmitter3 } = require("events");
66753
66814
  var { Worker } = require("worker_threads");
66754
- var { join: join21 } = require("path");
66815
+ var { join: join22 } = require("path");
66755
66816
  var { pathToFileURL } = require("url");
66756
66817
  var { wait } = require_wait();
66757
66818
  var {
@@ -66787,7 +66848,7 @@ var require_thread_stream = __commonJS({
66787
66848
  function createWorker(stream, opts) {
66788
66849
  const { filename, workerData } = opts;
66789
66850
  const bundlerOverrides = "__bundlerPathsOverrides" in globalThis ? globalThis.__bundlerPathsOverrides : {};
66790
- const toExecute = bundlerOverrides["thread-stream-worker"] || join21(__dirname, "lib", "worker.js");
66851
+ const toExecute = bundlerOverrides["thread-stream-worker"] || join22(__dirname, "lib", "worker.js");
66791
66852
  const worker = new Worker(toExecute, {
66792
66853
  ...opts.workerOpts,
66793
66854
  trackUnmanagedFds: false,
@@ -67178,7 +67239,7 @@ var require_transport = __commonJS({
67178
67239
  var { createRequire: createRequire2 } = require("module");
67179
67240
  var { existsSync: existsSync4 } = require("node:fs");
67180
67241
  var getCallers = require_caller();
67181
- var { join: join21, isAbsolute: isAbsolute2, sep: sep2 } = require("node:path");
67242
+ var { join: join22, isAbsolute: isAbsolute2, sep: sep2 } = require("node:path");
67182
67243
  var { fileURLToPath: fileURLToPath2 } = require("node:url");
67183
67244
  var sleep = require_atomic_sleep();
67184
67245
  var onExit = require_on_exit_leak_free();
@@ -67331,7 +67392,7 @@ var require_transport = __commonJS({
67331
67392
  throw new Error("only one of target or targets can be specified");
67332
67393
  }
67333
67394
  if (targets) {
67334
- target = bundlerOverrides["pino-worker"] || join21(__dirname, "worker.js");
67395
+ target = bundlerOverrides["pino-worker"] || join22(__dirname, "worker.js");
67335
67396
  options.targets = targets.filter((dest) => dest.target).map((dest) => {
67336
67397
  return {
67337
67398
  ...dest,
@@ -67349,7 +67410,7 @@ var require_transport = __commonJS({
67349
67410
  });
67350
67411
  });
67351
67412
  } else if (pipeline) {
67352
- target = bundlerOverrides["pino-worker"] || join21(__dirname, "worker.js");
67413
+ target = bundlerOverrides["pino-worker"] || join22(__dirname, "worker.js");
67353
67414
  options.pipelines = [pipeline.map((dest) => {
67354
67415
  return {
67355
67416
  ...dest,
@@ -67372,7 +67433,7 @@ var require_transport = __commonJS({
67372
67433
  return origin;
67373
67434
  }
67374
67435
  if (origin === "pino/file") {
67375
- return join21(__dirname, "..", "file.js");
67436
+ return join22(__dirname, "..", "file.js");
67376
67437
  }
67377
67438
  let fixTarget2;
67378
67439
  for (const filePath of callers) {
@@ -68362,7 +68423,7 @@ var require_safe_stable_stringify = __commonJS({
68362
68423
  return circularValue;
68363
68424
  }
68364
68425
  let res = "";
68365
- let join21 = ",";
68426
+ let join22 = ",";
68366
68427
  const originalIndentation = indentation;
68367
68428
  if (Array.isArray(value)) {
68368
68429
  if (value.length === 0) {
@@ -68376,7 +68437,7 @@ var require_safe_stable_stringify = __commonJS({
68376
68437
  indentation += spacer;
68377
68438
  res += `
68378
68439
  ${indentation}`;
68379
- join21 = `,
68440
+ join22 = `,
68380
68441
  ${indentation}`;
68381
68442
  }
68382
68443
  const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
@@ -68384,13 +68445,13 @@ ${indentation}`;
68384
68445
  for (; i < maximumValuesToStringify - 1; i++) {
68385
68446
  const tmp2 = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
68386
68447
  res += tmp2 !== void 0 ? tmp2 : "null";
68387
- res += join21;
68448
+ res += join22;
68388
68449
  }
68389
68450
  const tmp = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
68390
68451
  res += tmp !== void 0 ? tmp : "null";
68391
68452
  if (value.length - 1 > maximumBreadth) {
68392
68453
  const removedKeys = value.length - maximumBreadth - 1;
68393
- res += `${join21}"... ${getItemCount(removedKeys)} not stringified"`;
68454
+ res += `${join22}"... ${getItemCount(removedKeys)} not stringified"`;
68394
68455
  }
68395
68456
  if (spacer !== "") {
68396
68457
  res += `
@@ -68411,7 +68472,7 @@ ${originalIndentation}`;
68411
68472
  let separator = "";
68412
68473
  if (spacer !== "") {
68413
68474
  indentation += spacer;
68414
- join21 = `,
68475
+ join22 = `,
68415
68476
  ${indentation}`;
68416
68477
  whitespace = " ";
68417
68478
  }
@@ -68425,13 +68486,13 @@ ${indentation}`;
68425
68486
  const tmp = stringifyFnReplacer(key2, value, stack, replacer, spacer, indentation);
68426
68487
  if (tmp !== void 0) {
68427
68488
  res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
68428
- separator = join21;
68489
+ separator = join22;
68429
68490
  }
68430
68491
  }
68431
68492
  if (keyLength > maximumBreadth) {
68432
68493
  const removedKeys = keyLength - maximumBreadth;
68433
68494
  res += `${separator}"...":${whitespace}"${getItemCount(removedKeys)} not stringified"`;
68434
- separator = join21;
68495
+ separator = join22;
68435
68496
  }
68436
68497
  if (spacer !== "" && separator.length > 1) {
68437
68498
  res = `
@@ -68471,7 +68532,7 @@ ${originalIndentation}`;
68471
68532
  }
68472
68533
  const originalIndentation = indentation;
68473
68534
  let res = "";
68474
- let join21 = ",";
68535
+ let join22 = ",";
68475
68536
  if (Array.isArray(value)) {
68476
68537
  if (value.length === 0) {
68477
68538
  return "[]";
@@ -68484,7 +68545,7 @@ ${originalIndentation}`;
68484
68545
  indentation += spacer;
68485
68546
  res += `
68486
68547
  ${indentation}`;
68487
- join21 = `,
68548
+ join22 = `,
68488
68549
  ${indentation}`;
68489
68550
  }
68490
68551
  const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
@@ -68492,13 +68553,13 @@ ${indentation}`;
68492
68553
  for (; i < maximumValuesToStringify - 1; i++) {
68493
68554
  const tmp2 = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
68494
68555
  res += tmp2 !== void 0 ? tmp2 : "null";
68495
- res += join21;
68556
+ res += join22;
68496
68557
  }
68497
68558
  const tmp = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
68498
68559
  res += tmp !== void 0 ? tmp : "null";
68499
68560
  if (value.length - 1 > maximumBreadth) {
68500
68561
  const removedKeys = value.length - maximumBreadth - 1;
68501
- res += `${join21}"... ${getItemCount(removedKeys)} not stringified"`;
68562
+ res += `${join22}"... ${getItemCount(removedKeys)} not stringified"`;
68502
68563
  }
68503
68564
  if (spacer !== "") {
68504
68565
  res += `
@@ -68511,7 +68572,7 @@ ${originalIndentation}`;
68511
68572
  let whitespace = "";
68512
68573
  if (spacer !== "") {
68513
68574
  indentation += spacer;
68514
- join21 = `,
68575
+ join22 = `,
68515
68576
  ${indentation}`;
68516
68577
  whitespace = " ";
68517
68578
  }
@@ -68520,7 +68581,7 @@ ${indentation}`;
68520
68581
  const tmp = stringifyArrayReplacer(key2, value[key2], stack, replacer, spacer, indentation);
68521
68582
  if (tmp !== void 0) {
68522
68583
  res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
68523
- separator = join21;
68584
+ separator = join22;
68524
68585
  }
68525
68586
  }
68526
68587
  if (spacer !== "" && separator.length > 1) {
@@ -68577,20 +68638,20 @@ ${originalIndentation}`;
68577
68638
  indentation += spacer;
68578
68639
  let res2 = `
68579
68640
  ${indentation}`;
68580
- const join22 = `,
68641
+ const join23 = `,
68581
68642
  ${indentation}`;
68582
68643
  const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
68583
68644
  let i = 0;
68584
68645
  for (; i < maximumValuesToStringify - 1; i++) {
68585
68646
  const tmp2 = stringifyIndent(String(i), value[i], stack, spacer, indentation);
68586
68647
  res2 += tmp2 !== void 0 ? tmp2 : "null";
68587
- res2 += join22;
68648
+ res2 += join23;
68588
68649
  }
68589
68650
  const tmp = stringifyIndent(String(i), value[i], stack, spacer, indentation);
68590
68651
  res2 += tmp !== void 0 ? tmp : "null";
68591
68652
  if (value.length - 1 > maximumBreadth) {
68592
68653
  const removedKeys = value.length - maximumBreadth - 1;
68593
- res2 += `${join22}"... ${getItemCount(removedKeys)} not stringified"`;
68654
+ res2 += `${join23}"... ${getItemCount(removedKeys)} not stringified"`;
68594
68655
  }
68595
68656
  res2 += `
68596
68657
  ${originalIndentation}`;
@@ -68606,16 +68667,16 @@ ${originalIndentation}`;
68606
68667
  return '"[Object]"';
68607
68668
  }
68608
68669
  indentation += spacer;
68609
- const join21 = `,
68670
+ const join22 = `,
68610
68671
  ${indentation}`;
68611
68672
  let res = "";
68612
68673
  let separator = "";
68613
68674
  let maximumPropertiesToStringify = Math.min(keyLength, maximumBreadth);
68614
68675
  if (isTypedArrayWithEntries(value)) {
68615
- res += stringifyTypedArray(value, join21, maximumBreadth);
68676
+ res += stringifyTypedArray(value, join22, maximumBreadth);
68616
68677
  keys = keys.slice(value.length);
68617
68678
  maximumPropertiesToStringify -= value.length;
68618
- separator = join21;
68679
+ separator = join22;
68619
68680
  }
68620
68681
  if (deterministic) {
68621
68682
  keys = sort(keys, comparator);
@@ -68626,13 +68687,13 @@ ${indentation}`;
68626
68687
  const tmp = stringifyIndent(key2, value[key2], stack, spacer, indentation);
68627
68688
  if (tmp !== void 0) {
68628
68689
  res += `${separator}${strEscape(key2)}: ${tmp}`;
68629
- separator = join21;
68690
+ separator = join22;
68630
68691
  }
68631
68692
  }
68632
68693
  if (keyLength > maximumBreadth) {
68633
68694
  const removedKeys = keyLength - maximumBreadth;
68634
68695
  res += `${separator}"...": "${getItemCount(removedKeys)} not stringified"`;
68635
- separator = join21;
68696
+ separator = join22;
68636
68697
  }
68637
68698
  if (separator !== "") {
68638
68699
  res = `
@@ -103100,7 +103161,7 @@ var require_send = __commonJS({
103100
103161
  var { parseTokenList } = require_parseTokenList();
103101
103162
  var { createHttpError } = require_createHttpError();
103102
103163
  var extname4 = path24.extname;
103103
- var join21 = path24.join;
103164
+ var join22 = path24.join;
103104
103165
  var normalize2 = path24.normalize;
103105
103166
  var resolve11 = path24.resolve;
103106
103167
  var sep2 = path24.sep;
@@ -103187,7 +103248,7 @@ var require_send = __commonJS({
103187
103248
  return { statusCode: 403 };
103188
103249
  }
103189
103250
  parts = path25.split(sep2);
103190
- path25 = normalize2(join21(root, path25));
103251
+ path25 = normalize2(join22(root, path25));
103191
103252
  } else {
103192
103253
  if (UP_PATH_REGEXP.test(path25)) {
103193
103254
  debug('malicious path "%s"', path25);
@@ -103471,7 +103532,7 @@ var require_send = __commonJS({
103471
103532
  let err;
103472
103533
  for (let i = 0; i < options.index.length; i++) {
103473
103534
  const index = options.index[i];
103474
- const p = join21(path25, index);
103535
+ const p = join22(path25, index);
103475
103536
  const { error: error49, stat: stat4 } = await tryStat(p);
103476
103537
  if (error49) {
103477
103538
  err = error49;
@@ -111887,6 +111948,13 @@ function setupWatcher(ctx, server) {
111887
111948
  ctx.wss.close();
111888
111949
  });
111889
111950
  }
111951
+ const configFilePath = path17.join(ctx.workspaceRoot, ".kanban.json");
111952
+ const configWatcher = esm_default.watch(configFilePath, {
111953
+ ignoreInitial: true,
111954
+ awaitWriteFinish: { stabilityThreshold: 100 }
111955
+ });
111956
+ configWatcher.on("change", () => handleFileChange(ctx, debounceRef));
111957
+ server.on("close", () => configWatcher.close());
111890
111958
  }
111891
111959
  var fs10, path17;
111892
111960
  var init_watcherSetup = __esm({
@@ -112092,6 +112160,23 @@ var init_lifecycle = __esm({
112092
112160
  });
112093
112161
 
112094
112162
  // src/standalone/internal/runtime.ts
112163
+ function getIndexHtml(basePath = "") {
112164
+ return `<!DOCTYPE html>
112165
+ <html lang="en">
112166
+ <head>
112167
+ <meta charset="UTF-8">
112168
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
112169
+ <link rel="icon" type="image/svg+xml" href="${basePath}/favicon.svg">
112170
+ <link href="${basePath}/style.css" rel="stylesheet">
112171
+ <title>Kanban Board</title>
112172
+ <script>window.__KB_BASE__ = ${JSON.stringify(basePath)}</script>
112173
+ </head>
112174
+ <body>
112175
+ <div id="root"></div>
112176
+ <script type="module" src="${basePath}/index.js"></script>
112177
+ </body>
112178
+ </html>`;
112179
+ }
112095
112180
  function resolveStandaloneWebviewDir(webviewDir) {
112096
112181
  if (webviewDir)
112097
112182
  return webviewDir;
@@ -112105,12 +112190,12 @@ function resolveStandaloneWebviewDir(webviewDir) {
112105
112190
  }
112106
112191
  return candidates[0];
112107
112192
  }
112108
- function createStandaloneRuntime(kanbanDir, webviewDir, httpServer) {
112193
+ function createStandaloneRuntime(kanbanDir, webviewDir, httpServer, basePath) {
112109
112194
  const absoluteKanbanDir = path19.resolve(kanbanDir);
112110
112195
  const workspaceRoot = path19.dirname(absoluteKanbanDir);
112111
112196
  const resolvedWebviewDir = resolveStandaloneWebviewDir(webviewDir);
112112
112197
  const server = httpServer ?? http.createServer();
112113
- const wss = new import_websocket_server.default({ server, path: "/ws" });
112198
+ const wss = new import_websocket_server.default({ server, path: (basePath || "") + "/ws" });
112114
112199
  const ctx = {};
112115
112200
  const sdk = new KanbanSDK(absoluteKanbanDir, {
112116
112201
  onEvent: (event, data) => {
@@ -112159,20 +112244,7 @@ var init_runtime = __esm({
112159
112244
  init_wrapper();
112160
112245
  init_KanbanSDK();
112161
112246
  init_broadcastService();
112162
- indexHtml = `<!DOCTYPE html>
112163
- <html lang="en">
112164
- <head>
112165
- <meta charset="UTF-8">
112166
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
112167
- <link rel="icon" type="image/svg+xml" href="/favicon.svg">
112168
- <link href="/style.css" rel="stylesheet">
112169
- <title>Kanban Board</title>
112170
- </head>
112171
- <body>
112172
- <div id="root"></div>
112173
- <script type="module" src="/index.js"></script>
112174
- </body>
112175
- </html>`;
112247
+ indexHtml = getIndexHtml();
112176
112248
  }
112177
112249
  });
112178
112250
 
@@ -114380,10 +114452,13 @@ function startServer(kanbanDir, port, webviewDir, resolvedConfigPath) {
114380
114452
  const swaggerUiStaticDir = resolveSwaggerUiStaticDir();
114381
114453
  const swaggerUiLogoPath = swaggerUiStaticDir ? path21.join(swaggerUiStaticDir, "logo.svg") : void 0;
114382
114454
  const swaggerUiLogo = swaggerUiLogoPath && fs15.existsSync(swaggerUiLogoPath) ? { type: "image/svg+xml", content: fs15.readFileSync(swaggerUiLogoPath) } : null;
114383
- const runtime = createStandaloneRuntime(kanbanDir, webviewDir, fastify.server);
114455
+ const workspaceRoot = path21.dirname(path21.resolve(kanbanDir));
114456
+ const config3 = readConfig(workspaceRoot);
114457
+ const rawBase = config3.basePath ?? "";
114458
+ const basePath = rawBase ? (rawBase.startsWith("/") ? rawBase : "/" + rawBase).replace(/\/+$/, "") : "";
114459
+ const runtime = createStandaloneRuntime(kanbanDir, webviewDir, fastify.server, basePath);
114384
114460
  const { ctx, resolvedWebviewDir } = runtime;
114385
- const config3 = readConfig(ctx.workspaceRoot);
114386
- let resolvedIndexHtml = indexHtml;
114461
+ let resolvedIndexHtml = getIndexHtml(basePath);
114387
114462
  let customHead = config3.customHeadHtml || "";
114388
114463
  if (config3.customHeadHtmlFile) {
114389
114464
  try {
@@ -114393,14 +114468,14 @@ function startServer(kanbanDir, port, webviewDir, resolvedConfigPath) {
114393
114468
  }
114394
114469
  }
114395
114470
  if (customHead) {
114396
- resolvedIndexHtml = indexHtml.replace("</head>", `${customHead}
114471
+ resolvedIndexHtml = resolvedIndexHtml.replace("</head>", `${customHead}
114397
114472
  </head>`);
114398
114473
  }
114399
114474
  const standaloneHttpPlugins = ctx.sdk.capabilities?.standaloneHttpPlugins ?? [];
114400
114475
  const standaloneOpenApiSpec = buildStandaloneOpenApiSpec(standaloneHttpPlugins);
114401
114476
  fastify.register(import_swagger.default, { openapi: standaloneOpenApiSpec });
114402
114477
  fastify.register(import_swagger_ui.default, {
114403
- routePrefix: "/api/docs",
114478
+ routePrefix: `${basePath}/api/docs`,
114404
114479
  uiConfig: { docExpansion: "list", deepLinking: false },
114405
114480
  logo: swaggerUiLogo,
114406
114481
  ...swaggerUiStaticDir ? { baseDir: swaggerUiStaticDir } : {}
@@ -114432,6 +114507,14 @@ function startServer(kanbanDir, port, webviewDir, resolvedConfigPath) {
114432
114507
  if (request.body instanceof Buffer && request.body.length > 0) {
114433
114508
  req._rawBody = request.body;
114434
114509
  }
114510
+ if (basePath) {
114511
+ const rawUrl = req.url ?? "/";
114512
+ if (rawUrl === basePath) {
114513
+ req.url = "/";
114514
+ } else if (rawUrl.startsWith(basePath + "/") || rawUrl.startsWith(basePath + "?")) {
114515
+ req.url = rawUrl.slice(basePath.length);
114516
+ }
114517
+ }
114435
114518
  const requestContext = createRequestContext(ctx, req, reply.raw, resolvedWebviewDir, resolvedIndexHtml);
114436
114519
  await dispatchRequest(requestContext, middlewareHandlers);
114437
114520
  if (!reply.sent && !reply.raw.writableEnded) {
@@ -114447,7 +114530,7 @@ function startServer(kanbanDir, port, webviewDir, resolvedConfigPath) {
114447
114530
  console.error("Failed to start server:", err);
114448
114531
  process.exit(1);
114449
114532
  }
114450
- console.log(`Kanban board running at http://localhost:${port}`);
114533
+ console.log(`Kanban board running at http://localhost:${port}${basePath}`);
114451
114534
  console.log(`API available at http://localhost:${port}/api`);
114452
114535
  console.log(`Kanban config: ${effectiveConfigPath}`);
114453
114536
  console.log(`Kanban directory: ${ctx.absoluteKanbanDir}`);