@xwang152/claw-lark 0.1.12 → 0.1.13

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.
@@ -0,0 +1,212 @@
1
+ /**
2
+ * Lark Message Monitor
3
+ *
4
+ * Listens for incoming messages via WebSocket or webhook
5
+ * and routes them to the OpenClaw message handler.
6
+ */
7
+ import { createLarkWSClient, createEventDispatcher, createLarkClient } from "./client.js";
8
+ import { getLarkRuntime } from "./runtime.js";
9
+ import { startWebhookServer } from "./webhook.js";
10
+ // Active connections for cleanup
11
+ let activeWsClient = null;
12
+ let activeWebhookServer = null;
13
+ /**
14
+ * Extract plain text from Lark message content.
15
+ * Handles different message types (text, post/rich-text).
16
+ */
17
+ export function extractText(content, messageType) {
18
+ try {
19
+ const parsed = JSON.parse(content);
20
+ if (messageType === "text") {
21
+ // Preserve mentions in text as @Name or <at id="xxx">@Name</at>
22
+ return parsed.text ?? "";
23
+ }
24
+ if (messageType === "post") {
25
+ // Rich text - flatten and extract text elements, including mentions
26
+ if (Array.isArray(parsed.content)) {
27
+ return parsed.content
28
+ .flat()
29
+ .map((item) => {
30
+ if (item.tag === "text")
31
+ return item.text;
32
+ if (item.tag === "at")
33
+ return item.user_name || `@${item.user_id}`;
34
+ if (item.tag === "a")
35
+ return `${item.text} (${item.href})`;
36
+ return "";
37
+ })
38
+ .join("");
39
+ }
40
+ return parsed.title ?? "";
41
+ }
42
+ // Unsupported message type - return placeholder
43
+ return `[${messageType} message]`;
44
+ }
45
+ catch {
46
+ // If JSON parsing fails, return raw content
47
+ return content;
48
+ }
49
+ }
50
+ /**
51
+ * Parse Lark message event into normalized format.
52
+ * Returns null for empty or invalid messages.
53
+ */
54
+ export function parseMessage(event) {
55
+ const { message, sender } = event;
56
+ const text = extractText(message.content, message.message_type);
57
+ const media = [];
58
+ try {
59
+ const parsed = JSON.parse(message.content);
60
+ if (message.message_type === "image") {
61
+ media.push({
62
+ type: "image",
63
+ id: parsed.image_key,
64
+ });
65
+ }
66
+ else if (message.message_type === "file") {
67
+ media.push({
68
+ type: "file",
69
+ id: parsed.file_key,
70
+ fileName: parsed.file_name,
71
+ });
72
+ }
73
+ }
74
+ catch (error) {
75
+ // Ignore parsing errors for media
76
+ }
77
+ // If no text and no media, skip
78
+ if (!text.trim() && media.length === 0) {
79
+ return null;
80
+ }
81
+ return {
82
+ messageId: message.message_id,
83
+ chatId: message.chat_id,
84
+ chatType: message.chat_type,
85
+ senderId: sender.sender_id.open_id,
86
+ senderType: sender.sender_type,
87
+ text: text || `[${message.message_type}]`,
88
+ threadId: message.root_id,
89
+ mentions: (message.mentions ?? []).map((m) => ({
90
+ id: m.id.open_id,
91
+ name: m.name,
92
+ })),
93
+ timestamp: parseInt(message.create_time, 10),
94
+ media: media.length > 0 ? media : undefined,
95
+ };
96
+ }
97
+ /**
98
+ * Route incoming message to OpenClaw handler.
99
+ */
100
+ export async function routeMessage(event, account) {
101
+ const api = getLarkRuntime();
102
+ const parsed = parseMessage(event);
103
+ if (!parsed) {
104
+ api.logger.debug("[feishu] Skipping empty or unparseable message");
105
+ return;
106
+ }
107
+ api.logger.info(`[feishu] Received message from ${parsed.senderId} in ${parsed.chatType} ${parsed.chatId}`);
108
+ // Add "typing" indicator (reaction)
109
+ const client = createLarkClient(account);
110
+ try {
111
+ await client.im.messageReaction.create({
112
+ path: { message_id: parsed.messageId },
113
+ data: {
114
+ reaction_type: {
115
+ emoji_type: "TYPING", // Custom type or a common emoji like "COMMUNICATING"
116
+ },
117
+ },
118
+ });
119
+ }
120
+ catch (e) {
121
+ // Ignore reaction errors
122
+ }
123
+ await api.inbound.handleMessage({
124
+ channel: "feishu",
125
+ accountId: account.accountId,
126
+ messageId: parsed.messageId,
127
+ chatId: parsed.chatId,
128
+ chatType: parsed.chatType === "p2p" ? "direct" : "group",
129
+ senderId: parsed.senderId,
130
+ text: parsed.text,
131
+ threadId: parsed.threadId,
132
+ timestamp: parsed.timestamp,
133
+ mentions: parsed.mentions,
134
+ media: parsed.media,
135
+ raw: event,
136
+ });
137
+ }
138
+ /**
139
+ * Start WebSocket connection and listen for messages.
140
+ */
141
+ async function startWebSocket(account, abortSignal) {
142
+ const api = getLarkRuntime();
143
+ api.logger.info(`[feishu] Starting WebSocket connection for account: ${account.accountId}`);
144
+ const wsClient = createLarkWSClient(account);
145
+ activeWsClient = wsClient;
146
+ const dispatcher = createEventDispatcher(account.encryptKey, account.verificationToken).register({
147
+ "im.message.receive_v1": async (data) => {
148
+ try {
149
+ await routeMessage(data, account);
150
+ }
151
+ catch (error) {
152
+ api.logger.error(`[feishu] Error handling message: ${error}`);
153
+ }
154
+ },
155
+ });
156
+ try {
157
+ await wsClient.start({ eventDispatcher: dispatcher });
158
+ api.logger.info("[feishu] WebSocket connection established");
159
+ }
160
+ catch (error) {
161
+ api.logger.error(`[feishu] WebSocket start failed: ${error}`);
162
+ throw error;
163
+ }
164
+ // Handle abort signal for graceful shutdown
165
+ if (abortSignal) {
166
+ abortSignal.addEventListener("abort", () => {
167
+ api.logger.info("[feishu] Stopping WebSocket (abort signal received)");
168
+ stopMonitor();
169
+ }, { once: true });
170
+ }
171
+ }
172
+ /**
173
+ * Start monitoring for Lark messages.
174
+ *
175
+ * Supports both WebSocket and webhook modes:
176
+ * - WebSocket: Long connection (enterprise accounts)
177
+ * - Webhook: HTTP callback (individual accounts, requires ngrok)
178
+ */
179
+ export async function monitorLarkProvider(options) {
180
+ const { account, abortSignal } = options;
181
+ const api = getLarkRuntime();
182
+ if (account.connectionMode === "websocket") {
183
+ await startWebSocket(account, abortSignal);
184
+ return;
185
+ }
186
+ // Webhook mode - start HTTP server
187
+ api.logger.info("[feishu] Starting webhook server for individual account");
188
+ const webhookServer = await startWebhookServer({ account, abortSignal });
189
+ activeWebhookServer = webhookServer;
190
+ // Handle abort signal for graceful shutdown
191
+ if (abortSignal) {
192
+ abortSignal.addEventListener("abort", () => {
193
+ api.logger.info("[feishu] Stopping webhook server (abort signal received)");
194
+ webhookServer.stop();
195
+ activeWebhookServer = null;
196
+ }, { once: true });
197
+ }
198
+ }
199
+ /**
200
+ * Stop the active monitor connection.
201
+ */
202
+ export function stopMonitor() {
203
+ if (activeWsClient) {
204
+ activeWsClient.close({ force: true });
205
+ activeWsClient = null;
206
+ }
207
+ if (activeWebhookServer) {
208
+ activeWebhookServer.stop();
209
+ activeWebhookServer = null;
210
+ }
211
+ }
212
+ //# sourceMappingURL=monitor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"monitor.js","sourceRoot":"","sources":["../../src/monitor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Lark Plugin Runtime
3
+ *
4
+ * Manages the plugin API context provided by OpenClaw.
5
+ * The API is set once during plugin registration and
6
+ * provides access to logging, inbound message handling, etc.
7
+ */
8
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
9
+ /**
10
+ * Initialize the Lark plugin with the API context.
11
+ * Called once during plugin registration.
12
+ */
13
+ export declare function setLarkRuntime(api: OpenClawPluginApi): void;
14
+ /**
15
+ * Get the current plugin API.
16
+ * Throws if called before plugin registration.
17
+ */
18
+ export declare function getLarkRuntime(): OpenClawPluginApi;
19
+ /**
20
+ * Check if runtime has been initialized.
21
+ * Useful for conditional logic without throwing.
22
+ */
23
+ export declare function hasLarkRuntime(): boolean;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Lark Plugin Runtime
3
+ *
4
+ * Manages the plugin API context provided by OpenClaw.
5
+ * The API is set once during plugin registration and
6
+ * provides access to logging, inbound message handling, etc.
7
+ */
8
+ let pluginApi = null;
9
+ /**
10
+ * Initialize the Lark plugin with the API context.
11
+ * Called once during plugin registration.
12
+ */
13
+ export function setLarkRuntime(api) {
14
+ pluginApi = api;
15
+ }
16
+ /**
17
+ * Get the current plugin API.
18
+ * Throws if called before plugin registration.
19
+ */
20
+ export function getLarkRuntime() {
21
+ if (!pluginApi) {
22
+ throw new Error("Lark runtime not initialized. Ensure the plugin is properly registered.");
23
+ }
24
+ return pluginApi;
25
+ }
26
+ /**
27
+ * Check if runtime has been initialized.
28
+ * Useful for conditional logic without throwing.
29
+ */
30
+ export function hasLarkRuntime() {
31
+ return pluginApi !== null;
32
+ }
33
+ //# sourceMappingURL=runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../../src/runtime.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,IAAI,SAAS,GAA6B,IAAI,CAAC;AAE/C;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,GAAsB;IACnD,SAAS,GAAG,GAAG,CAAC;AAClB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc;IAC5B,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CACb,yEAAyE,CAC1E,CAAC;IACJ,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc;IAC5B,OAAO,SAAS,KAAK,IAAI,CAAC;AAC5B,CAAC"}
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Lark/Feishu Plugin Type Definitions
3
+ *
4
+ * Core types for Lark/Feishu integration with OpenClaw.
5
+ * Designed for clarity and minimal redundancy.
6
+ */
7
+ export type LarkDomain = "lark" | "feishu";
8
+ export type LarkConnectionMode = "websocket" | "webhook";
9
+ export type DmPolicy = "open" | "pairing" | "allowlist";
10
+ export type GroupPolicy = "open" | "allowlist" | "disabled";
11
+ export type RenderMode = "auto" | "raw" | "card";
12
+ /**
13
+ * Raw account configuration from channels.lark.accounts.<id>
14
+ * All fields are optional - defaults applied during resolution.
15
+ */
16
+ export interface LarkAccountConfig {
17
+ enabled?: boolean;
18
+ appId?: string;
19
+ appSecret?: string;
20
+ encryptKey?: string;
21
+ verificationToken?: string;
22
+ domain?: LarkDomain;
23
+ connectionMode?: LarkConnectionMode;
24
+ webhookPort?: number;
25
+ dmPolicy?: DmPolicy;
26
+ dmAllowlist?: string[];
27
+ groupPolicy?: GroupPolicy;
28
+ requireMention?: boolean;
29
+ groupAllowlist?: string[];
30
+ mediaMaxMb?: number;
31
+ renderMode?: RenderMode;
32
+ historyLimit?: number;
33
+ }
34
+ /**
35
+ * Fully resolved account with all defaults applied.
36
+ * Used throughout the plugin after initial resolution.
37
+ */
38
+ export interface ResolvedLarkAccount {
39
+ accountId: string;
40
+ enabled: boolean;
41
+ configured: boolean;
42
+ appId: string;
43
+ appSecret: string;
44
+ encryptKey?: string;
45
+ verificationToken?: string;
46
+ domain: LarkDomain;
47
+ connectionMode: LarkConnectionMode;
48
+ webhookPort: number;
49
+ dmPolicy: DmPolicy;
50
+ dmAllowlist: string[];
51
+ groupPolicy: GroupPolicy;
52
+ requireMention: boolean;
53
+ groupAllowlist: string[];
54
+ mediaMaxMb: number;
55
+ renderMode: RenderMode;
56
+ historyLimit: number;
57
+ }
58
+ /**
59
+ * Top-level Feishu channel configuration in channels.lark
60
+ */
61
+ export interface LarkChannelConfig {
62
+ enabled?: boolean;
63
+ accounts?: Record<string, LarkAccountConfig>;
64
+ }
65
+ /**
66
+ * Lark message event payload from im.message.receive_v1
67
+ * Matches the official Lark SDK event structure.
68
+ */
69
+ export interface LarkMessageEvent {
70
+ sender: {
71
+ sender_id: {
72
+ open_id: string;
73
+ user_id?: string;
74
+ union_id?: string;
75
+ };
76
+ sender_type: string;
77
+ tenant_key: string;
78
+ };
79
+ message: {
80
+ message_id: string;
81
+ root_id?: string;
82
+ parent_id?: string;
83
+ create_time: string;
84
+ update_time?: string;
85
+ chat_id: string;
86
+ chat_type: "p2p" | "group";
87
+ message_type: string;
88
+ content: string;
89
+ mentions?: LarkMention[];
90
+ };
91
+ }
92
+ /**
93
+ * Mention data within a Lark message
94
+ */
95
+ export interface LarkMention {
96
+ key: string;
97
+ id: {
98
+ open_id: string;
99
+ user_id?: string;
100
+ union_id?: string;
101
+ };
102
+ name: string;
103
+ tenant_key: string;
104
+ }
105
+ /**
106
+ * Normalized message format for OpenClaw routing.
107
+ */
108
+ export interface ParsedMessage {
109
+ messageId: string;
110
+ chatId: string;
111
+ chatType: "p2p" | "group";
112
+ senderId: string;
113
+ senderType: string;
114
+ text: string;
115
+ threadId?: string;
116
+ mentions: Array<{
117
+ id: string;
118
+ name: string;
119
+ }>;
120
+ timestamp: number;
121
+ media?: Array<{
122
+ type: "image" | "file";
123
+ id: string;
124
+ fileName?: string;
125
+ mimeType?: string;
126
+ size?: number;
127
+ }>;
128
+ }
129
+ /**
130
+ * Result of a message send operation.
131
+ */
132
+ export interface SendResult {
133
+ ok: boolean;
134
+ messageId?: string;
135
+ error?: string;
136
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Lark/Feishu Plugin Type Definitions
3
+ *
4
+ * Core types for Lark/Feishu integration with OpenClaw.
5
+ * Designed for clarity and minimal redundancy.
6
+ */
7
+ export {};
8
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Lark/Feishu Webhook Server
3
+ *
4
+ * Standalone HTTP server for receiving Lark/Feishu webhook callbacks.
5
+ * Used for individual accounts that don't support WebSocket.
6
+ * Includes automatic crash recovery.
7
+ */
8
+ import * as http from "node:http";
9
+ import type { ResolvedLarkAccount } from "./types.js";
10
+ interface WebhookServer {
11
+ server: http.Server | null;
12
+ port: number;
13
+ stop: () => void;
14
+ }
15
+ /**
16
+ * Start the webhook server.
17
+ */
18
+ export declare function startWebhookServer(args: {
19
+ account: ResolvedLarkAccount;
20
+ abortSignal?: AbortSignal;
21
+ }): Promise<WebhookServer>;
22
+ export {};
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Lark/Feishu Webhook Server
3
+ *
4
+ * Standalone HTTP server for receiving Lark/Feishu webhook callbacks.
5
+ * Used for individual accounts that don't support WebSocket.
6
+ * Includes automatic crash recovery.
7
+ */
8
+ import * as http from "node:http";
9
+ import * as crypto from "node:crypto";
10
+ import { getLarkRuntime } from "./runtime.js";
11
+ import { routeMessage } from "./monitor.js";
12
+ const RESTART_DELAY_MS = 3000;
13
+ const MAX_RESTART_ATTEMPTS = 5;
14
+ /**
15
+ * Decrypt AES-256-CBC encrypted event data from Lark.
16
+ */
17
+ function decryptEvent(encrypted, encryptKey) {
18
+ const key = crypto.createHash("sha256").update(encryptKey).digest();
19
+ const encryptedBuffer = Buffer.from(encrypted, "base64");
20
+ const iv = encryptedBuffer.subarray(0, 16);
21
+ const ciphertext = encryptedBuffer.subarray(16);
22
+ const decipher = crypto.createDecipheriv("aes-256-cbc", key, iv);
23
+ let decrypted = decipher.update(ciphertext, undefined, "utf8");
24
+ decrypted += decipher.final("utf8");
25
+ return decrypted;
26
+ }
27
+ /**
28
+ * Parse request body as JSON.
29
+ */
30
+ async function parseBody(req) {
31
+ return new Promise((resolve, reject) => {
32
+ const chunks = [];
33
+ req.on("data", (chunk) => chunks.push(chunk));
34
+ req.on("end", () => {
35
+ try {
36
+ const body = Buffer.concat(chunks).toString("utf8");
37
+ if (!body)
38
+ return resolve({});
39
+ resolve(JSON.parse(body));
40
+ }
41
+ catch (err) {
42
+ reject(err);
43
+ }
44
+ });
45
+ req.on("error", reject);
46
+ });
47
+ }
48
+ /**
49
+ * Start the webhook server.
50
+ */
51
+ export async function startWebhookServer(args) {
52
+ const { account, abortSignal } = args;
53
+ const api = getLarkRuntime();
54
+ const port = account.webhookPort;
55
+ let restartAttempts = 0;
56
+ const server = http.createServer(async (req, res) => {
57
+ if (req.method !== "POST") {
58
+ res.statusCode = 405;
59
+ res.end("Method Not Allowed");
60
+ return;
61
+ }
62
+ try {
63
+ let body = await parseBody(req);
64
+ // Handle encrypted events
65
+ if (body.encrypt && account.encryptKey) {
66
+ const decrypted = decryptEvent(body.encrypt, account.encryptKey);
67
+ body = JSON.parse(decrypted);
68
+ }
69
+ // Handle URL challenge
70
+ if (body.type === "url_verification") {
71
+ res.setHeader("Content-Type", "application/json");
72
+ res.end(JSON.stringify({ challenge: body.challenge }));
73
+ return;
74
+ }
75
+ // Verify token
76
+ if (account.verificationToken && body.header?.token !== account.verificationToken) {
77
+ api.logger.warn("[feishu] Webhook token mismatch");
78
+ res.statusCode = 401;
79
+ res.end("Unauthorized");
80
+ return;
81
+ }
82
+ // Process event
83
+ const eventType = body.header?.event_type;
84
+ if (eventType === "im.message.receive_v1") {
85
+ await routeMessage(body.event, account);
86
+ }
87
+ res.statusCode = 200;
88
+ res.end("OK");
89
+ }
90
+ catch (error) {
91
+ api.logger.error(`[feishu] Webhook error: ${error}`);
92
+ res.statusCode = 500;
93
+ res.end("Internal Server Error");
94
+ }
95
+ });
96
+ const stop = () => {
97
+ if (server.listening) {
98
+ server.close();
99
+ api.logger.info(`[feishu] Webhook server stopped on port ${port}`);
100
+ }
101
+ };
102
+ abortSignal?.addEventListener("abort", stop);
103
+ const start = () => {
104
+ server.listen(port, () => {
105
+ api.logger.info(`[feishu] Webhook server listening on port ${port}`);
106
+ restartAttempts = 0;
107
+ });
108
+ };
109
+ server.on("error", (err) => {
110
+ api.logger.error(`[feishu] Webhook server error: ${err}`);
111
+ if (err.code === "EADDRINUSE") {
112
+ api.logger.error(`[feishu] Port ${port} is already in use`);
113
+ return;
114
+ }
115
+ if (restartAttempts < MAX_RESTART_ATTEMPTS) {
116
+ restartAttempts++;
117
+ api.logger.info(`[feishu] Restarting webhook server in ${RESTART_DELAY_MS}ms...`);
118
+ setTimeout(start, RESTART_DELAY_MS);
119
+ }
120
+ else {
121
+ api.logger.error("[feishu] Max webhook server restart attempts reached");
122
+ }
123
+ });
124
+ start();
125
+ return {
126
+ server,
127
+ port,
128
+ stop,
129
+ };
130
+ }
131
+ //# sourceMappingURL=webhook.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook.js","sourceRoot":"","sources":["../../src/webhook.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xwang152/claw-lark",
3
- "version": "0.1.12",
3
+ "version": "0.1.13",
4
4
  "type": "module",
5
5
  "description": "Lark/Feishu channel plugin for OpenClaw with WebSocket and Webhook support",
6
6
  "license": "MIT",
@@ -25,8 +25,16 @@
25
25
  "./dist/index.js"
26
26
  ]
27
27
  },
28
+ "files": [
29
+ "dist",
30
+ "openclaw.plugin.json",
31
+ "README.md",
32
+ "README_zh.md",
33
+ "LICENSE"
34
+ ],
28
35
  "scripts": {
29
36
  "build": "tsc -p tsconfig.json",
37
+ "prepublishOnly": "npm run build",
30
38
  "typecheck": "tsc -p tsconfig.json --noEmit",
31
39
  "lint": "eslint src/**/*.ts index.ts --no-warn-ignored",
32
40
  "format": "prettier --write src/**/*.ts index.ts",
package/.eslintrc.json DELETED
@@ -1,18 +0,0 @@
1
- {
2
- "parser": "@typescript-eslint/parser",
3
- "extends": [
4
- "eslint:recommended",
5
- "plugin:@typescript-eslint/recommended",
6
- "plugin:prettier/recommended"
7
- ],
8
- "plugins": ["@typescript-eslint", "prettier"],
9
- "rules": {
10
- "prettier/prettier": "error",
11
- "@typescript-eslint/no-explicit-any": "warn",
12
- "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }]
13
- },
14
- "env": {
15
- "node": true,
16
- "es2022": true
17
- }
18
- }
@@ -1,39 +0,0 @@
1
- name: Node.js Package Publish
2
-
3
- on:
4
- push:
5
- branches:
6
- - main
7
-
8
- jobs:
9
- publish:
10
- runs-on: ubuntu-latest
11
- steps:
12
- - uses: actions/checkout@v4
13
-
14
- - uses: actions/setup-node@v4
15
- with:
16
- node-version: '20.x'
17
- registry-url: 'https://registry.npmjs.org'
18
-
19
- - name: Install dependencies
20
- run: npm install
21
-
22
- - name: Check if version exists on npm
23
- id: check_version
24
- run: |
25
- VERSION=$(node -p "require('./package.json').version")
26
- NAME=$(node -p "require('./package.json').name")
27
- if npm view "$NAME@$VERSION" version >/dev/null 2>&1; then
28
- echo "Version $VERSION already exists on npm. Skipping publish."
29
- echo "exists=true" >> $GITHUB_OUTPUT
30
- else
31
- echo "Version $VERSION is new. Proceeding to publish."
32
- echo "exists=false" >> $GITHUB_OUTPUT
33
- fi
34
-
35
- - name: Publish to npm
36
- if: steps.check_version.outputs.exists == 'false'
37
- run: npm publish --access public
38
- env:
39
- NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}