kanban-lite 1.2.2 → 1.2.4
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 +257 -90
- package/dist/extension.js +245 -78
- package/dist/mcp-server.js +117 -33
- package/dist/sdk/index.cjs +117 -32
- package/dist/sdk/index.mjs +117 -32
- package/dist/sdk/sdk/KanbanSDK.d.ts +27 -10
- package/dist/sdk/sdk/modules/cards.d.ts +11 -2
- package/dist/sdk/sdk/plugins/index.d.ts +7 -0
- package/dist/sdk/sdk/types.d.ts +12 -27
- package/dist/sdk/shared/config.d.ts +17 -1
- package/dist/standalone-webview/index.js +38 -38
- package/dist/standalone-webview/index.js.map +1 -1
- package/dist/standalone.js +307 -125
- package/package.json +1 -1
- package/src/cli/index.test.ts +157 -0
- package/src/cli/index.ts +1 -1
- package/src/mcp-server/index.test.ts +76 -0
- package/src/mcp-server/index.ts +1 -1
- package/src/sdk/KanbanSDK.d.ts +26 -10
- package/src/sdk/KanbanSDK.ts +37 -11
- package/src/sdk/__tests__/KanbanSDK.test.ts +79 -24
- package/src/sdk/integrationCatalog.ts +1 -0
- package/src/sdk/modules/cards.ts +13 -24
- package/src/sdk/plugins/index.d.ts +7 -0
- package/src/sdk/plugins/index.ts +17 -2
- package/src/sdk/types.d.ts +10 -26
- package/src/sdk/types.ts +11 -24
- package/src/sdk/webhooks.ts +19 -2
- package/src/shared/config.ts +130 -2
- package/src/standalone/__tests__/server.integration.test.ts +81 -2
- package/src/standalone/internal/runtime.ts +11 -6
- package/src/standalone/internal/websocket.ts +13 -3
- package/src/standalone/server.ts +67 -9
- package/src/standalone/watcherSetup.ts +9 -0
- package/src/webview/standalone-shim.ts +2 -1
- package/tmp/screenshots-workspace/.kanban/.active-card.json +5 -0
- package/tmp/screenshots-workspace/.kanban/boards/default/deleted/1-dddd.md +17 -0
- package/tmp/screenshots-workspace/.kanban/boards/default/deleted/attachments/1.log +1 -0
- package/tmp/screenshots-workspace/.kanban.json +59 -0
package/dist/mcp-server.js
CHANGED
|
@@ -17664,11 +17664,87 @@ function migrateConfigV1ToV2(raw) {
|
|
|
17664
17664
|
}
|
|
17665
17665
|
return v2;
|
|
17666
17666
|
}
|
|
17667
|
+
function loadDotEnv(dir) {
|
|
17668
|
+
const envPath = path.join(dir, ".env");
|
|
17669
|
+
let content;
|
|
17670
|
+
try {
|
|
17671
|
+
content = fs.readFileSync(envPath, "utf-8");
|
|
17672
|
+
} catch {
|
|
17673
|
+
return;
|
|
17674
|
+
}
|
|
17675
|
+
for (const line of content.split("\n")) {
|
|
17676
|
+
const trimmed = line.trim();
|
|
17677
|
+
if (!trimmed || trimmed.startsWith("#"))
|
|
17678
|
+
continue;
|
|
17679
|
+
const eqIdx = trimmed.indexOf("=");
|
|
17680
|
+
if (eqIdx < 1)
|
|
17681
|
+
continue;
|
|
17682
|
+
const key = trimmed.slice(0, eqIdx).trim();
|
|
17683
|
+
let val = trimmed.slice(eqIdx + 1).trim();
|
|
17684
|
+
if (val.startsWith('"') && val.endsWith('"') || val.startsWith("'") && val.endsWith("'")) {
|
|
17685
|
+
val = val.slice(1, -1);
|
|
17686
|
+
}
|
|
17687
|
+
if (process.env[key] === void 0) {
|
|
17688
|
+
process.env[key] = val;
|
|
17689
|
+
}
|
|
17690
|
+
}
|
|
17691
|
+
}
|
|
17692
|
+
function resolveConfigEnvVars(node, configFileName, nodePath = "") {
|
|
17693
|
+
const isFormDefaultDataPath = /^\.forms\.(?:[^.]+|"[^"]+")\.data(?:$|[.\[])/.test(nodePath);
|
|
17694
|
+
if (isFormDefaultDataPath) {
|
|
17695
|
+
return node;
|
|
17696
|
+
}
|
|
17697
|
+
if (typeof node === "string") {
|
|
17698
|
+
return node.replace(/\$\{([^}]+)\}/g, (_match, varName) => {
|
|
17699
|
+
const envValue = process.env[varName];
|
|
17700
|
+
if (envValue === void 0) {
|
|
17701
|
+
throw new Error(
|
|
17702
|
+
`missing ${varName} in ${configFileName}: ${nodePath} "${node}"`
|
|
17703
|
+
);
|
|
17704
|
+
}
|
|
17705
|
+
return envValue;
|
|
17706
|
+
});
|
|
17707
|
+
}
|
|
17708
|
+
if (Array.isArray(node)) {
|
|
17709
|
+
for (let i = 0; i < node.length; i++) {
|
|
17710
|
+
node[i] = resolveConfigEnvVars(node[i], configFileName, `${nodePath}[${i}]`);
|
|
17711
|
+
}
|
|
17712
|
+
return node;
|
|
17713
|
+
}
|
|
17714
|
+
if (node !== null && typeof node === "object") {
|
|
17715
|
+
const obj = node;
|
|
17716
|
+
for (const key of Object.keys(obj)) {
|
|
17717
|
+
const jsonKey = /[^a-zA-Z0-9_]/.test(key) ? `"${key}"` : key;
|
|
17718
|
+
const childPath = nodePath ? `${nodePath}.${jsonKey}` : `.${jsonKey}`;
|
|
17719
|
+
obj[key] = resolveConfigEnvVars(obj[key], configFileName, childPath);
|
|
17720
|
+
}
|
|
17721
|
+
return obj;
|
|
17722
|
+
}
|
|
17723
|
+
return node;
|
|
17724
|
+
}
|
|
17667
17725
|
function readConfig(workspaceRoot) {
|
|
17668
17726
|
const filePath = configPath(workspaceRoot);
|
|
17669
17727
|
const defaults = { ...DEFAULT_CONFIG, boards: { default: { ...DEFAULT_BOARD_CONFIG, columns: [...DEFAULT_COLUMNS] } } };
|
|
17728
|
+
let raw;
|
|
17729
|
+
try {
|
|
17730
|
+
raw = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
17731
|
+
} catch {
|
|
17732
|
+
return defaults;
|
|
17733
|
+
}
|
|
17734
|
+
loadDotEnv(workspaceRoot);
|
|
17735
|
+
try {
|
|
17736
|
+
resolveConfigEnvVars(raw, CONFIG_FILENAME);
|
|
17737
|
+
} catch (err) {
|
|
17738
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
17739
|
+
process.stderr.write(`
|
|
17740
|
+
Configuration error: ${msg}
|
|
17741
|
+
|
|
17742
|
+
Set the missing environment variable before starting the server.
|
|
17743
|
+
|
|
17744
|
+
`);
|
|
17745
|
+
process.exit(1);
|
|
17746
|
+
}
|
|
17670
17747
|
try {
|
|
17671
|
-
const raw = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
17672
17748
|
const isV1 = raw.version === 1 || !raw.version && !(typeof raw.boards === "object" && raw.boards !== null && !Array.isArray(raw.boards));
|
|
17673
17749
|
if (isV1) {
|
|
17674
17750
|
const v2 = migrateConfigV1ToV2(raw);
|
|
@@ -21268,7 +21344,7 @@ function resolveAuthIdentityPlugin(ref) {
|
|
|
21268
21344
|
if (ref.provider === "rbac")
|
|
21269
21345
|
return RBAC_IDENTITY_PLUGIN;
|
|
21270
21346
|
const packageName = AUTH_PROVIDER_ALIASES.get(ref.provider) ?? ref.provider;
|
|
21271
|
-
return loadExternalAuthIdentityPlugin(packageName, ref.provider);
|
|
21347
|
+
return loadExternalAuthIdentityPlugin(packageName, ref.provider, ref.options);
|
|
21272
21348
|
}
|
|
21273
21349
|
function resolveAuthPolicyPlugin(ref) {
|
|
21274
21350
|
if (ref.provider === "noop")
|
|
@@ -21494,8 +21570,13 @@ function selectAuthPolicyPlugin(mod, providerId) {
|
|
|
21494
21570
|
return direct;
|
|
21495
21571
|
return null;
|
|
21496
21572
|
}
|
|
21497
|
-
function loadExternalAuthIdentityPlugin(packageName, providerId) {
|
|
21573
|
+
function loadExternalAuthIdentityPlugin(packageName, providerId, options) {
|
|
21498
21574
|
const mod = loadExternalModule(packageName);
|
|
21575
|
+
if (options !== void 0 && typeof mod.createAuthIdentityPlugin === "function") {
|
|
21576
|
+
const created = mod.createAuthIdentityPlugin(options);
|
|
21577
|
+
if (isValidAuthIdentityPlugin(created, providerId))
|
|
21578
|
+
return created;
|
|
21579
|
+
}
|
|
21499
21580
|
const plugin = selectAuthIdentityPlugin(mod, providerId);
|
|
21500
21581
|
if (!plugin) {
|
|
21501
21582
|
throw new Error(
|
|
@@ -24565,29 +24646,10 @@ async function updateCard(ctx, { cardId, updates, boardId }) {
|
|
|
24565
24646
|
return card;
|
|
24566
24647
|
}
|
|
24567
24648
|
async function triggerAction(ctx, { cardId, action, boardId }) {
|
|
24568
|
-
const config = readConfig(ctx.workspaceRoot);
|
|
24569
|
-
const { actionWebhookUrl } = config;
|
|
24570
|
-
if (!actionWebhookUrl) {
|
|
24571
|
-
throw new Error("No action webhook URL configured. Set actionWebhookUrl in .kanban.json");
|
|
24572
|
-
}
|
|
24573
24649
|
const card = await getCard(ctx, { cardId, boardId });
|
|
24574
24650
|
if (!card)
|
|
24575
24651
|
throw new Error(`Card not found: ${cardId}`);
|
|
24576
24652
|
const resolvedBoardId = card.boardId || ctx._resolveBoardId(boardId);
|
|
24577
|
-
const payload = {
|
|
24578
|
-
action,
|
|
24579
|
-
board: resolvedBoardId,
|
|
24580
|
-
list: card.status,
|
|
24581
|
-
card: sanitizeCard(card)
|
|
24582
|
-
};
|
|
24583
|
-
const response = await fetch(actionWebhookUrl, {
|
|
24584
|
-
method: "POST",
|
|
24585
|
-
headers: { "Content-Type": "application/json" },
|
|
24586
|
-
body: JSON.stringify(payload)
|
|
24587
|
-
});
|
|
24588
|
-
if (!response.ok) {
|
|
24589
|
-
throw new Error(`Action webhook responded with ${response.status}: ${response.statusText}`);
|
|
24590
|
-
}
|
|
24591
24653
|
await appendActivityLog(ctx, {
|
|
24592
24654
|
cardId,
|
|
24593
24655
|
boardId: resolvedBoardId,
|
|
@@ -24598,6 +24660,12 @@ async function triggerAction(ctx, { cardId, action, boardId }) {
|
|
|
24598
24660
|
}
|
|
24599
24661
|
}).catch(() => {
|
|
24600
24662
|
});
|
|
24663
|
+
return {
|
|
24664
|
+
action,
|
|
24665
|
+
board: resolvedBoardId,
|
|
24666
|
+
list: card.status,
|
|
24667
|
+
card: sanitizeCard(card)
|
|
24668
|
+
};
|
|
24601
24669
|
}
|
|
24602
24670
|
async function submitForm(ctx, input) {
|
|
24603
24671
|
const card = await getCard(ctx, { cardId: input.cardId, boardId: input.boardId });
|
|
@@ -26014,6 +26082,25 @@ var KanbanSDK = class _KanbanSDK {
|
|
|
26014
26082
|
get workspaceRoot() {
|
|
26015
26083
|
return path14.dirname(this.kanbanDir);
|
|
26016
26084
|
}
|
|
26085
|
+
/**
|
|
26086
|
+
* Returns a cloned read-only snapshot of the current workspace config.
|
|
26087
|
+
*
|
|
26088
|
+
* The returned snapshot is created from a fresh config read and deep-cloned
|
|
26089
|
+
* before being returned, so callers receive an isolated view of the current
|
|
26090
|
+
* `.kanban.json` state rather than a live mutable runtime object. Mutating the
|
|
26091
|
+
* returned snapshot does not update persisted config or affect this SDK instance.
|
|
26092
|
+
*
|
|
26093
|
+
* @returns A cloned read-only snapshot of the current {@link KanbanConfig}.
|
|
26094
|
+
*
|
|
26095
|
+
* @example
|
|
26096
|
+
* ```ts
|
|
26097
|
+
* const config = sdk.getConfigSnapshot()
|
|
26098
|
+
* console.log(config.defaultBoard)
|
|
26099
|
+
* ```
|
|
26100
|
+
*/
|
|
26101
|
+
getConfigSnapshot() {
|
|
26102
|
+
return structuredClone(readConfig(this.workspaceRoot));
|
|
26103
|
+
}
|
|
26017
26104
|
// --- Board resolution helpers ---
|
|
26018
26105
|
/** @internal */
|
|
26019
26106
|
_resolveBoardId(boardId) {
|
|
@@ -26480,21 +26567,17 @@ var KanbanSDK = class _KanbanSDK {
|
|
|
26480
26567
|
return result;
|
|
26481
26568
|
}
|
|
26482
26569
|
/**
|
|
26483
|
-
* Triggers a named action for a card
|
|
26484
|
-
* configured in `.kanban.json`.
|
|
26570
|
+
* Triggers a named action for a card.
|
|
26485
26571
|
*
|
|
26486
|
-
*
|
|
26487
|
-
*
|
|
26488
|
-
*
|
|
26489
|
-
* ```
|
|
26572
|
+
* Validates the card, appends an activity log entry, and emits the
|
|
26573
|
+
* `card.action.triggered` after-event so registered webhooks receive
|
|
26574
|
+
* the action payload automatically.
|
|
26490
26575
|
*
|
|
26491
26576
|
* @param cardId - The ID of the card to trigger the action for.
|
|
26492
26577
|
* @param action - The action name string (e.g. `'retry'`, `'sendEmail'`).
|
|
26493
26578
|
* @param boardId - Optional board ID. Defaults to the workspace's default board.
|
|
26494
|
-
* @returns A promise
|
|
26495
|
-
* @throws {Error} If no `actionWebhookUrl` is configured in `.kanban.json`.
|
|
26579
|
+
* @returns A promise that resolves when the action has been processed.
|
|
26496
26580
|
* @throws {Error} If the card is not found.
|
|
26497
|
-
* @throws {Error} If the webhook responds with a non-2xx status.
|
|
26498
26581
|
*
|
|
26499
26582
|
* @example
|
|
26500
26583
|
* ```ts
|
|
@@ -26504,7 +26587,8 @@ var KanbanSDK = class _KanbanSDK {
|
|
|
26504
26587
|
*/
|
|
26505
26588
|
async triggerAction(cardId, action, boardId) {
|
|
26506
26589
|
const mergedInput = await this._runBeforeEvent("card.action.trigger", { cardId, action, boardId }, void 0, boardId);
|
|
26507
|
-
|
|
26590
|
+
const payload = await triggerAction(this, mergedInput);
|
|
26591
|
+
this._runAfterEvent("card.action.triggered", payload, void 0, payload.board);
|
|
26508
26592
|
}
|
|
26509
26593
|
/**
|
|
26510
26594
|
* Moves a card to a different status column and/or position within that column.
|
|
@@ -28159,7 +28243,7 @@ async function main() {
|
|
|
28159
28243
|
);
|
|
28160
28244
|
server.tool(
|
|
28161
28245
|
"trigger_action",
|
|
28162
|
-
"Trigger a named action on a card. The action name must match one of the card's configured actions.
|
|
28246
|
+
"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.",
|
|
28163
28247
|
{
|
|
28164
28248
|
card_id: import_zod.z.string().describe("Card ID (partial match supported)"),
|
|
28165
28249
|
action: import_zod.z.string().describe("Action name to trigger"),
|
package/dist/sdk/index.cjs
CHANGED
|
@@ -17694,11 +17694,87 @@ function migrateConfigV1ToV2(raw) {
|
|
|
17694
17694
|
}
|
|
17695
17695
|
return v2;
|
|
17696
17696
|
}
|
|
17697
|
+
function loadDotEnv(dir) {
|
|
17698
|
+
const envPath = path.join(dir, ".env");
|
|
17699
|
+
let content;
|
|
17700
|
+
try {
|
|
17701
|
+
content = fs.readFileSync(envPath, "utf-8");
|
|
17702
|
+
} catch {
|
|
17703
|
+
return;
|
|
17704
|
+
}
|
|
17705
|
+
for (const line of content.split("\n")) {
|
|
17706
|
+
const trimmed = line.trim();
|
|
17707
|
+
if (!trimmed || trimmed.startsWith("#"))
|
|
17708
|
+
continue;
|
|
17709
|
+
const eqIdx = trimmed.indexOf("=");
|
|
17710
|
+
if (eqIdx < 1)
|
|
17711
|
+
continue;
|
|
17712
|
+
const key = trimmed.slice(0, eqIdx).trim();
|
|
17713
|
+
let val = trimmed.slice(eqIdx + 1).trim();
|
|
17714
|
+
if (val.startsWith('"') && val.endsWith('"') || val.startsWith("'") && val.endsWith("'")) {
|
|
17715
|
+
val = val.slice(1, -1);
|
|
17716
|
+
}
|
|
17717
|
+
if (process.env[key] === void 0) {
|
|
17718
|
+
process.env[key] = val;
|
|
17719
|
+
}
|
|
17720
|
+
}
|
|
17721
|
+
}
|
|
17722
|
+
function resolveConfigEnvVars(node, configFileName, nodePath = "") {
|
|
17723
|
+
const isFormDefaultDataPath = /^\.forms\.(?:[^.]+|"[^"]+")\.data(?:$|[.\[])/.test(nodePath);
|
|
17724
|
+
if (isFormDefaultDataPath) {
|
|
17725
|
+
return node;
|
|
17726
|
+
}
|
|
17727
|
+
if (typeof node === "string") {
|
|
17728
|
+
return node.replace(/\$\{([^}]+)\}/g, (_match, varName) => {
|
|
17729
|
+
const envValue = process.env[varName];
|
|
17730
|
+
if (envValue === void 0) {
|
|
17731
|
+
throw new Error(
|
|
17732
|
+
`missing ${varName} in ${configFileName}: ${nodePath} "${node}"`
|
|
17733
|
+
);
|
|
17734
|
+
}
|
|
17735
|
+
return envValue;
|
|
17736
|
+
});
|
|
17737
|
+
}
|
|
17738
|
+
if (Array.isArray(node)) {
|
|
17739
|
+
for (let i = 0; i < node.length; i++) {
|
|
17740
|
+
node[i] = resolveConfigEnvVars(node[i], configFileName, `${nodePath}[${i}]`);
|
|
17741
|
+
}
|
|
17742
|
+
return node;
|
|
17743
|
+
}
|
|
17744
|
+
if (node !== null && typeof node === "object") {
|
|
17745
|
+
const obj = node;
|
|
17746
|
+
for (const key of Object.keys(obj)) {
|
|
17747
|
+
const jsonKey = /[^a-zA-Z0-9_]/.test(key) ? `"${key}"` : key;
|
|
17748
|
+
const childPath = nodePath ? `${nodePath}.${jsonKey}` : `.${jsonKey}`;
|
|
17749
|
+
obj[key] = resolveConfigEnvVars(obj[key], configFileName, childPath);
|
|
17750
|
+
}
|
|
17751
|
+
return obj;
|
|
17752
|
+
}
|
|
17753
|
+
return node;
|
|
17754
|
+
}
|
|
17697
17755
|
function readConfig(workspaceRoot) {
|
|
17698
17756
|
const filePath = configPath(workspaceRoot);
|
|
17699
17757
|
const defaults = { ...DEFAULT_CONFIG, boards: { default: { ...DEFAULT_BOARD_CONFIG, columns: [...DEFAULT_COLUMNS] } } };
|
|
17758
|
+
let raw;
|
|
17759
|
+
try {
|
|
17760
|
+
raw = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
17761
|
+
} catch {
|
|
17762
|
+
return defaults;
|
|
17763
|
+
}
|
|
17764
|
+
loadDotEnv(workspaceRoot);
|
|
17765
|
+
try {
|
|
17766
|
+
resolveConfigEnvVars(raw, CONFIG_FILENAME);
|
|
17767
|
+
} catch (err) {
|
|
17768
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
17769
|
+
process.stderr.write(`
|
|
17770
|
+
Configuration error: ${msg}
|
|
17771
|
+
|
|
17772
|
+
Set the missing environment variable before starting the server.
|
|
17773
|
+
|
|
17774
|
+
`);
|
|
17775
|
+
process.exit(1);
|
|
17776
|
+
}
|
|
17700
17777
|
try {
|
|
17701
|
-
const raw = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
17702
17778
|
const isV1 = raw.version === 1 || !raw.version && !(typeof raw.boards === "object" && raw.boards !== null && !Array.isArray(raw.boards));
|
|
17703
17779
|
if (isV1) {
|
|
17704
17780
|
const v2 = migrateConfigV1ToV2(raw);
|
|
@@ -21302,7 +21378,7 @@ function resolveAuthIdentityPlugin(ref) {
|
|
|
21302
21378
|
if (ref.provider === "rbac")
|
|
21303
21379
|
return RBAC_IDENTITY_PLUGIN;
|
|
21304
21380
|
const packageName = AUTH_PROVIDER_ALIASES.get(ref.provider) ?? ref.provider;
|
|
21305
|
-
return loadExternalAuthIdentityPlugin(packageName, ref.provider);
|
|
21381
|
+
return loadExternalAuthIdentityPlugin(packageName, ref.provider, ref.options);
|
|
21306
21382
|
}
|
|
21307
21383
|
function resolveAuthPolicyPlugin(ref) {
|
|
21308
21384
|
if (ref.provider === "noop")
|
|
@@ -21528,8 +21604,13 @@ function selectAuthPolicyPlugin(mod, providerId) {
|
|
|
21528
21604
|
return direct;
|
|
21529
21605
|
return null;
|
|
21530
21606
|
}
|
|
21531
|
-
function loadExternalAuthIdentityPlugin(packageName, providerId) {
|
|
21607
|
+
function loadExternalAuthIdentityPlugin(packageName, providerId, options) {
|
|
21532
21608
|
const mod = loadExternalModule(packageName);
|
|
21609
|
+
if (options !== void 0 && typeof mod.createAuthIdentityPlugin === "function") {
|
|
21610
|
+
const created = mod.createAuthIdentityPlugin(options);
|
|
21611
|
+
if (isValidAuthIdentityPlugin(created, providerId))
|
|
21612
|
+
return created;
|
|
21613
|
+
}
|
|
21533
21614
|
const plugin = selectAuthIdentityPlugin(mod, providerId);
|
|
21534
21615
|
if (!plugin) {
|
|
21535
21616
|
throw new Error(
|
|
@@ -24532,29 +24613,10 @@ async function updateCard(ctx, { cardId, updates, boardId }) {
|
|
|
24532
24613
|
return card;
|
|
24533
24614
|
}
|
|
24534
24615
|
async function triggerAction(ctx, { cardId, action, boardId }) {
|
|
24535
|
-
const config = readConfig(ctx.workspaceRoot);
|
|
24536
|
-
const { actionWebhookUrl } = config;
|
|
24537
|
-
if (!actionWebhookUrl) {
|
|
24538
|
-
throw new Error("No action webhook URL configured. Set actionWebhookUrl in .kanban.json");
|
|
24539
|
-
}
|
|
24540
24616
|
const card = await getCard(ctx, { cardId, boardId });
|
|
24541
24617
|
if (!card)
|
|
24542
24618
|
throw new Error(`Card not found: ${cardId}`);
|
|
24543
24619
|
const resolvedBoardId = card.boardId || ctx._resolveBoardId(boardId);
|
|
24544
|
-
const payload = {
|
|
24545
|
-
action,
|
|
24546
|
-
board: resolvedBoardId,
|
|
24547
|
-
list: card.status,
|
|
24548
|
-
card: sanitizeCard(card)
|
|
24549
|
-
};
|
|
24550
|
-
const response = await fetch(actionWebhookUrl, {
|
|
24551
|
-
method: "POST",
|
|
24552
|
-
headers: { "Content-Type": "application/json" },
|
|
24553
|
-
body: JSON.stringify(payload)
|
|
24554
|
-
});
|
|
24555
|
-
if (!response.ok) {
|
|
24556
|
-
throw new Error(`Action webhook responded with ${response.status}: ${response.statusText}`);
|
|
24557
|
-
}
|
|
24558
24620
|
await appendActivityLog(ctx, {
|
|
24559
24621
|
cardId,
|
|
24560
24622
|
boardId: resolvedBoardId,
|
|
@@ -24565,6 +24627,12 @@ async function triggerAction(ctx, { cardId, action, boardId }) {
|
|
|
24565
24627
|
}
|
|
24566
24628
|
}).catch(() => {
|
|
24567
24629
|
});
|
|
24630
|
+
return {
|
|
24631
|
+
action,
|
|
24632
|
+
board: resolvedBoardId,
|
|
24633
|
+
list: card.status,
|
|
24634
|
+
card: sanitizeCard(card)
|
|
24635
|
+
};
|
|
24568
24636
|
}
|
|
24569
24637
|
async function submitForm(ctx, input) {
|
|
24570
24638
|
const card = await getCard(ctx, { cardId: input.cardId, boardId: input.boardId });
|
|
@@ -25981,6 +26049,25 @@ var KanbanSDK = class _KanbanSDK {
|
|
|
25981
26049
|
get workspaceRoot() {
|
|
25982
26050
|
return path14.dirname(this.kanbanDir);
|
|
25983
26051
|
}
|
|
26052
|
+
/**
|
|
26053
|
+
* Returns a cloned read-only snapshot of the current workspace config.
|
|
26054
|
+
*
|
|
26055
|
+
* The returned snapshot is created from a fresh config read and deep-cloned
|
|
26056
|
+
* before being returned, so callers receive an isolated view of the current
|
|
26057
|
+
* `.kanban.json` state rather than a live mutable runtime object. Mutating the
|
|
26058
|
+
* returned snapshot does not update persisted config or affect this SDK instance.
|
|
26059
|
+
*
|
|
26060
|
+
* @returns A cloned read-only snapshot of the current {@link KanbanConfig}.
|
|
26061
|
+
*
|
|
26062
|
+
* @example
|
|
26063
|
+
* ```ts
|
|
26064
|
+
* const config = sdk.getConfigSnapshot()
|
|
26065
|
+
* console.log(config.defaultBoard)
|
|
26066
|
+
* ```
|
|
26067
|
+
*/
|
|
26068
|
+
getConfigSnapshot() {
|
|
26069
|
+
return structuredClone(readConfig(this.workspaceRoot));
|
|
26070
|
+
}
|
|
25984
26071
|
// --- Board resolution helpers ---
|
|
25985
26072
|
/** @internal */
|
|
25986
26073
|
_resolveBoardId(boardId) {
|
|
@@ -26447,21 +26534,17 @@ var KanbanSDK = class _KanbanSDK {
|
|
|
26447
26534
|
return result;
|
|
26448
26535
|
}
|
|
26449
26536
|
/**
|
|
26450
|
-
* Triggers a named action for a card
|
|
26451
|
-
* configured in `.kanban.json`.
|
|
26537
|
+
* Triggers a named action for a card.
|
|
26452
26538
|
*
|
|
26453
|
-
*
|
|
26454
|
-
*
|
|
26455
|
-
*
|
|
26456
|
-
* ```
|
|
26539
|
+
* Validates the card, appends an activity log entry, and emits the
|
|
26540
|
+
* `card.action.triggered` after-event so registered webhooks receive
|
|
26541
|
+
* the action payload automatically.
|
|
26457
26542
|
*
|
|
26458
26543
|
* @param cardId - The ID of the card to trigger the action for.
|
|
26459
26544
|
* @param action - The action name string (e.g. `'retry'`, `'sendEmail'`).
|
|
26460
26545
|
* @param boardId - Optional board ID. Defaults to the workspace's default board.
|
|
26461
|
-
* @returns A promise
|
|
26462
|
-
* @throws {Error} If no `actionWebhookUrl` is configured in `.kanban.json`.
|
|
26546
|
+
* @returns A promise that resolves when the action has been processed.
|
|
26463
26547
|
* @throws {Error} If the card is not found.
|
|
26464
|
-
* @throws {Error} If the webhook responds with a non-2xx status.
|
|
26465
26548
|
*
|
|
26466
26549
|
* @example
|
|
26467
26550
|
* ```ts
|
|
@@ -26471,7 +26554,8 @@ var KanbanSDK = class _KanbanSDK {
|
|
|
26471
26554
|
*/
|
|
26472
26555
|
async triggerAction(cardId, action, boardId) {
|
|
26473
26556
|
const mergedInput = await this._runBeforeEvent("card.action.trigger", { cardId, action, boardId }, void 0, boardId);
|
|
26474
|
-
|
|
26557
|
+
const payload = await triggerAction(this, mergedInput);
|
|
26558
|
+
this._runAfterEvent("card.action.triggered", payload, void 0, payload.board);
|
|
26475
26559
|
}
|
|
26476
26560
|
/**
|
|
26477
26561
|
* Moves a card to a different status column and/or position within that column.
|
|
@@ -27519,6 +27603,7 @@ var AFTER_ENTRIES = [
|
|
|
27519
27603
|
{ event: "board.updated", resource: "board", label: "Board updated" },
|
|
27520
27604
|
{ event: "board.deleted", resource: "board", label: "Board deleted" },
|
|
27521
27605
|
{ event: "board.action", resource: "board", label: "Board action triggered" },
|
|
27606
|
+
{ event: "card.action.triggered", resource: "card", label: "Card action triggered" },
|
|
27522
27607
|
{ event: "board.log.added", resource: "board", label: "Board log added" },
|
|
27523
27608
|
{ event: "board.log.cleared", resource: "board", label: "Board log cleared" },
|
|
27524
27609
|
// card logs
|
package/dist/sdk/index.mjs
CHANGED
|
@@ -17653,11 +17653,87 @@ function migrateConfigV1ToV2(raw) {
|
|
|
17653
17653
|
}
|
|
17654
17654
|
return v2;
|
|
17655
17655
|
}
|
|
17656
|
+
function loadDotEnv(dir) {
|
|
17657
|
+
const envPath = path.join(dir, ".env");
|
|
17658
|
+
let content;
|
|
17659
|
+
try {
|
|
17660
|
+
content = fs.readFileSync(envPath, "utf-8");
|
|
17661
|
+
} catch {
|
|
17662
|
+
return;
|
|
17663
|
+
}
|
|
17664
|
+
for (const line of content.split("\n")) {
|
|
17665
|
+
const trimmed = line.trim();
|
|
17666
|
+
if (!trimmed || trimmed.startsWith("#"))
|
|
17667
|
+
continue;
|
|
17668
|
+
const eqIdx = trimmed.indexOf("=");
|
|
17669
|
+
if (eqIdx < 1)
|
|
17670
|
+
continue;
|
|
17671
|
+
const key = trimmed.slice(0, eqIdx).trim();
|
|
17672
|
+
let val = trimmed.slice(eqIdx + 1).trim();
|
|
17673
|
+
if (val.startsWith('"') && val.endsWith('"') || val.startsWith("'") && val.endsWith("'")) {
|
|
17674
|
+
val = val.slice(1, -1);
|
|
17675
|
+
}
|
|
17676
|
+
if (process.env[key] === void 0) {
|
|
17677
|
+
process.env[key] = val;
|
|
17678
|
+
}
|
|
17679
|
+
}
|
|
17680
|
+
}
|
|
17681
|
+
function resolveConfigEnvVars(node, configFileName, nodePath = "") {
|
|
17682
|
+
const isFormDefaultDataPath = /^\.forms\.(?:[^.]+|"[^"]+")\.data(?:$|[.\[])/.test(nodePath);
|
|
17683
|
+
if (isFormDefaultDataPath) {
|
|
17684
|
+
return node;
|
|
17685
|
+
}
|
|
17686
|
+
if (typeof node === "string") {
|
|
17687
|
+
return node.replace(/\$\{([^}]+)\}/g, (_match, varName) => {
|
|
17688
|
+
const envValue = process.env[varName];
|
|
17689
|
+
if (envValue === void 0) {
|
|
17690
|
+
throw new Error(
|
|
17691
|
+
`missing ${varName} in ${configFileName}: ${nodePath} "${node}"`
|
|
17692
|
+
);
|
|
17693
|
+
}
|
|
17694
|
+
return envValue;
|
|
17695
|
+
});
|
|
17696
|
+
}
|
|
17697
|
+
if (Array.isArray(node)) {
|
|
17698
|
+
for (let i = 0; i < node.length; i++) {
|
|
17699
|
+
node[i] = resolveConfigEnvVars(node[i], configFileName, `${nodePath}[${i}]`);
|
|
17700
|
+
}
|
|
17701
|
+
return node;
|
|
17702
|
+
}
|
|
17703
|
+
if (node !== null && typeof node === "object") {
|
|
17704
|
+
const obj = node;
|
|
17705
|
+
for (const key of Object.keys(obj)) {
|
|
17706
|
+
const jsonKey = /[^a-zA-Z0-9_]/.test(key) ? `"${key}"` : key;
|
|
17707
|
+
const childPath = nodePath ? `${nodePath}.${jsonKey}` : `.${jsonKey}`;
|
|
17708
|
+
obj[key] = resolveConfigEnvVars(obj[key], configFileName, childPath);
|
|
17709
|
+
}
|
|
17710
|
+
return obj;
|
|
17711
|
+
}
|
|
17712
|
+
return node;
|
|
17713
|
+
}
|
|
17656
17714
|
function readConfig(workspaceRoot) {
|
|
17657
17715
|
const filePath = configPath(workspaceRoot);
|
|
17658
17716
|
const defaults = { ...DEFAULT_CONFIG, boards: { default: { ...DEFAULT_BOARD_CONFIG, columns: [...DEFAULT_COLUMNS] } } };
|
|
17717
|
+
let raw;
|
|
17718
|
+
try {
|
|
17719
|
+
raw = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
17720
|
+
} catch {
|
|
17721
|
+
return defaults;
|
|
17722
|
+
}
|
|
17723
|
+
loadDotEnv(workspaceRoot);
|
|
17724
|
+
try {
|
|
17725
|
+
resolveConfigEnvVars(raw, CONFIG_FILENAME);
|
|
17726
|
+
} catch (err) {
|
|
17727
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
17728
|
+
process.stderr.write(`
|
|
17729
|
+
Configuration error: ${msg}
|
|
17730
|
+
|
|
17731
|
+
Set the missing environment variable before starting the server.
|
|
17732
|
+
|
|
17733
|
+
`);
|
|
17734
|
+
process.exit(1);
|
|
17735
|
+
}
|
|
17659
17736
|
try {
|
|
17660
|
-
const raw = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
17661
17737
|
const isV1 = raw.version === 1 || !raw.version && !(typeof raw.boards === "object" && raw.boards !== null && !Array.isArray(raw.boards));
|
|
17662
17738
|
if (isV1) {
|
|
17663
17739
|
const v2 = migrateConfigV1ToV2(raw);
|
|
@@ -21261,7 +21337,7 @@ function resolveAuthIdentityPlugin(ref) {
|
|
|
21261
21337
|
if (ref.provider === "rbac")
|
|
21262
21338
|
return RBAC_IDENTITY_PLUGIN;
|
|
21263
21339
|
const packageName = AUTH_PROVIDER_ALIASES.get(ref.provider) ?? ref.provider;
|
|
21264
|
-
return loadExternalAuthIdentityPlugin(packageName, ref.provider);
|
|
21340
|
+
return loadExternalAuthIdentityPlugin(packageName, ref.provider, ref.options);
|
|
21265
21341
|
}
|
|
21266
21342
|
function resolveAuthPolicyPlugin(ref) {
|
|
21267
21343
|
if (ref.provider === "noop")
|
|
@@ -21487,8 +21563,13 @@ function selectAuthPolicyPlugin(mod, providerId) {
|
|
|
21487
21563
|
return direct;
|
|
21488
21564
|
return null;
|
|
21489
21565
|
}
|
|
21490
|
-
function loadExternalAuthIdentityPlugin(packageName, providerId) {
|
|
21566
|
+
function loadExternalAuthIdentityPlugin(packageName, providerId, options) {
|
|
21491
21567
|
const mod = loadExternalModule(packageName);
|
|
21568
|
+
if (options !== void 0 && typeof mod.createAuthIdentityPlugin === "function") {
|
|
21569
|
+
const created = mod.createAuthIdentityPlugin(options);
|
|
21570
|
+
if (isValidAuthIdentityPlugin(created, providerId))
|
|
21571
|
+
return created;
|
|
21572
|
+
}
|
|
21492
21573
|
const plugin = selectAuthIdentityPlugin(mod, providerId);
|
|
21493
21574
|
if (!plugin) {
|
|
21494
21575
|
throw new Error(
|
|
@@ -24491,29 +24572,10 @@ async function updateCard(ctx, { cardId, updates, boardId }) {
|
|
|
24491
24572
|
return card;
|
|
24492
24573
|
}
|
|
24493
24574
|
async function triggerAction(ctx, { cardId, action, boardId }) {
|
|
24494
|
-
const config = readConfig(ctx.workspaceRoot);
|
|
24495
|
-
const { actionWebhookUrl } = config;
|
|
24496
|
-
if (!actionWebhookUrl) {
|
|
24497
|
-
throw new Error("No action webhook URL configured. Set actionWebhookUrl in .kanban.json");
|
|
24498
|
-
}
|
|
24499
24575
|
const card = await getCard(ctx, { cardId, boardId });
|
|
24500
24576
|
if (!card)
|
|
24501
24577
|
throw new Error(`Card not found: ${cardId}`);
|
|
24502
24578
|
const resolvedBoardId = card.boardId || ctx._resolveBoardId(boardId);
|
|
24503
|
-
const payload = {
|
|
24504
|
-
action,
|
|
24505
|
-
board: resolvedBoardId,
|
|
24506
|
-
list: card.status,
|
|
24507
|
-
card: sanitizeCard(card)
|
|
24508
|
-
};
|
|
24509
|
-
const response = await fetch(actionWebhookUrl, {
|
|
24510
|
-
method: "POST",
|
|
24511
|
-
headers: { "Content-Type": "application/json" },
|
|
24512
|
-
body: JSON.stringify(payload)
|
|
24513
|
-
});
|
|
24514
|
-
if (!response.ok) {
|
|
24515
|
-
throw new Error(`Action webhook responded with ${response.status}: ${response.statusText}`);
|
|
24516
|
-
}
|
|
24517
24579
|
await appendActivityLog(ctx, {
|
|
24518
24580
|
cardId,
|
|
24519
24581
|
boardId: resolvedBoardId,
|
|
@@ -24524,6 +24586,12 @@ async function triggerAction(ctx, { cardId, action, boardId }) {
|
|
|
24524
24586
|
}
|
|
24525
24587
|
}).catch(() => {
|
|
24526
24588
|
});
|
|
24589
|
+
return {
|
|
24590
|
+
action,
|
|
24591
|
+
board: resolvedBoardId,
|
|
24592
|
+
list: card.status,
|
|
24593
|
+
card: sanitizeCard(card)
|
|
24594
|
+
};
|
|
24527
24595
|
}
|
|
24528
24596
|
async function submitForm(ctx, input) {
|
|
24529
24597
|
const card = await getCard(ctx, { cardId: input.cardId, boardId: input.boardId });
|
|
@@ -25940,6 +26008,25 @@ var KanbanSDK = class _KanbanSDK {
|
|
|
25940
26008
|
get workspaceRoot() {
|
|
25941
26009
|
return path14.dirname(this.kanbanDir);
|
|
25942
26010
|
}
|
|
26011
|
+
/**
|
|
26012
|
+
* Returns a cloned read-only snapshot of the current workspace config.
|
|
26013
|
+
*
|
|
26014
|
+
* The returned snapshot is created from a fresh config read and deep-cloned
|
|
26015
|
+
* before being returned, so callers receive an isolated view of the current
|
|
26016
|
+
* `.kanban.json` state rather than a live mutable runtime object. Mutating the
|
|
26017
|
+
* returned snapshot does not update persisted config or affect this SDK instance.
|
|
26018
|
+
*
|
|
26019
|
+
* @returns A cloned read-only snapshot of the current {@link KanbanConfig}.
|
|
26020
|
+
*
|
|
26021
|
+
* @example
|
|
26022
|
+
* ```ts
|
|
26023
|
+
* const config = sdk.getConfigSnapshot()
|
|
26024
|
+
* console.log(config.defaultBoard)
|
|
26025
|
+
* ```
|
|
26026
|
+
*/
|
|
26027
|
+
getConfigSnapshot() {
|
|
26028
|
+
return structuredClone(readConfig(this.workspaceRoot));
|
|
26029
|
+
}
|
|
25943
26030
|
// --- Board resolution helpers ---
|
|
25944
26031
|
/** @internal */
|
|
25945
26032
|
_resolveBoardId(boardId) {
|
|
@@ -26406,21 +26493,17 @@ var KanbanSDK = class _KanbanSDK {
|
|
|
26406
26493
|
return result;
|
|
26407
26494
|
}
|
|
26408
26495
|
/**
|
|
26409
|
-
* Triggers a named action for a card
|
|
26410
|
-
* configured in `.kanban.json`.
|
|
26496
|
+
* Triggers a named action for a card.
|
|
26411
26497
|
*
|
|
26412
|
-
*
|
|
26413
|
-
*
|
|
26414
|
-
*
|
|
26415
|
-
* ```
|
|
26498
|
+
* Validates the card, appends an activity log entry, and emits the
|
|
26499
|
+
* `card.action.triggered` after-event so registered webhooks receive
|
|
26500
|
+
* the action payload automatically.
|
|
26416
26501
|
*
|
|
26417
26502
|
* @param cardId - The ID of the card to trigger the action for.
|
|
26418
26503
|
* @param action - The action name string (e.g. `'retry'`, `'sendEmail'`).
|
|
26419
26504
|
* @param boardId - Optional board ID. Defaults to the workspace's default board.
|
|
26420
|
-
* @returns A promise
|
|
26421
|
-
* @throws {Error} If no `actionWebhookUrl` is configured in `.kanban.json`.
|
|
26505
|
+
* @returns A promise that resolves when the action has been processed.
|
|
26422
26506
|
* @throws {Error} If the card is not found.
|
|
26423
|
-
* @throws {Error} If the webhook responds with a non-2xx status.
|
|
26424
26507
|
*
|
|
26425
26508
|
* @example
|
|
26426
26509
|
* ```ts
|
|
@@ -26430,7 +26513,8 @@ var KanbanSDK = class _KanbanSDK {
|
|
|
26430
26513
|
*/
|
|
26431
26514
|
async triggerAction(cardId, action, boardId) {
|
|
26432
26515
|
const mergedInput = await this._runBeforeEvent("card.action.trigger", { cardId, action, boardId }, void 0, boardId);
|
|
26433
|
-
|
|
26516
|
+
const payload = await triggerAction(this, mergedInput);
|
|
26517
|
+
this._runAfterEvent("card.action.triggered", payload, void 0, payload.board);
|
|
26434
26518
|
}
|
|
26435
26519
|
/**
|
|
26436
26520
|
* Moves a card to a different status column and/or position within that column.
|
|
@@ -27478,6 +27562,7 @@ var AFTER_ENTRIES = [
|
|
|
27478
27562
|
{ event: "board.updated", resource: "board", label: "Board updated" },
|
|
27479
27563
|
{ event: "board.deleted", resource: "board", label: "Board deleted" },
|
|
27480
27564
|
{ event: "board.action", resource: "board", label: "Board action triggered" },
|
|
27565
|
+
{ event: "card.action.triggered", resource: "card", label: "Card action triggered" },
|
|
27481
27566
|
{ event: "board.log.added", resource: "board", label: "Board log added" },
|
|
27482
27567
|
{ event: "board.log.cleared", resource: "board", label: "Board log cleared" },
|
|
27483
27568
|
// card logs
|