homebridge-multiple-switch 1.2.0 → 1.3.1

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.
@@ -11,31 +11,20 @@ jobs:
11
11
  runs-on: ubuntu-latest
12
12
 
13
13
  steps:
14
- - name: 📥 Repo-nu klonla
15
- uses: actions/checkout@v3
14
+ - name: Checkout
15
+ uses: actions/checkout@v4
16
16
 
17
- - name: ⚙️ Node.js qur
18
- uses: actions/setup-node@v3
17
+ - name: Setup Node.js
18
+ uses: actions/setup-node@v4
19
19
  with:
20
20
  node-version: '18'
21
21
  cache: 'npm'
22
22
 
23
- - name: 📦 Asılılıqları qur
23
+ - name: Install dependencies
24
24
  run: npm install
25
25
 
26
- - name: ✅ Kod yoxlaması (lint varsa)
27
- run: |
28
- if [ -f .eslintrc.js ] || [ -f .eslintrc.json ]; then
29
- npm run lint
30
- else
31
- echo "Lint skripti tapılmadı, keçilir..."
32
- fi
33
-
34
- - name: 🧪 Testləri icra et (əgər varsa)
35
- run: |
36
- if [ -f package.json ] && grep -q '\"test\"' package.json; then
37
- npm test
38
- else
39
- echo "Test skripti yoxdur, keçilir..."
40
- fi
26
+ - name: Lint
27
+ run: npm run lint
41
28
 
29
+ - name: Test
30
+ run: npm test
@@ -1,4 +1,4 @@
1
- name: 📦 Publish to npm
1
+ name: Publish to npm
2
2
 
3
3
  on:
4
4
  push:
@@ -10,19 +10,19 @@ jobs:
10
10
  runs-on: ubuntu-latest
11
11
 
12
12
  steps:
13
- - name: 📥 Repo-nu klonla
14
- uses: actions/checkout@v3
13
+ - name: Checkout
14
+ uses: actions/checkout@v4
15
15
 
16
- - name: ⚙️ Node.js qur
17
- uses: actions/setup-node@v3
16
+ - name: Setup Node.js
17
+ uses: actions/setup-node@v4
18
18
  with:
19
19
  node-version: '18'
20
20
  registry-url: 'https://registry.npmjs.org/'
21
21
 
22
- - name: 📦 Asılılıqları qur
22
+ - name: Install dependencies
23
23
  run: npm install
24
24
 
25
- - name: 🚀 NPM-ə yüklə
25
+ - name: Publish to npm
26
26
  run: npm publish
27
27
  env:
28
28
  NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
package/CHANGELOG.md ADDED
@@ -0,0 +1,40 @@
1
+ # Changelog
2
+
3
+ ## [1.3.1] - 2026-03-21
4
+
5
+ ### Added
6
+ - i18n localization support for Homebridge UI config with 14 languages:
7
+ English, Turkish, German, French, Spanish, Portuguese, Italian, Russian,
8
+ Chinese (Simplified), Japanese, Korean, Polish, Dutch, Arabic
9
+ - Descriptions added to all config.schema.json fields
10
+
11
+ ## [1.3.0] - 2026-03-21
12
+
13
+ ### Changed
14
+ - Replaced global mutable variables with instance properties
15
+ - Refactored accessory setup to reuse cached accessories instead of creating duplicates on restart
16
+ - Extracted switch behavior logic into dedicated methods (`turnOffOthers`, `setAll`, `scheduleAutoOff`)
17
+ - Used `Map` for service and accessory tracking instead of plain objects
18
+ - Service type lookup uses a constant map instead of a switch statement
19
+ - Auto-off now checks current state before turning off (prevents stale timeouts)
20
+ - Updated minimum Node.js version from 14 to 18
21
+ - Updated GitHub Actions from v3 to v4
22
+ - Cleaned up CI workflow
23
+
24
+ ### Fixed
25
+ - `delayOff` not working in `master` mode due to if/else logic bug
26
+ - Cached accessories not being restored on Homebridge restart (caused duplicate accessories)
27
+ - Services stored in `accessory.context` which is not serializable
28
+ - README field names (`autoTurnOff`, `mode`) now match actual config schema (`delayOff`, `switchBehavior`)
29
+
30
+ ### Added
31
+ - Automatic removal of stale cached accessories when config changes
32
+ - Service reconciliation: adds new services and removes old ones on config update
33
+ - Validation for empty or missing switches array
34
+
35
+ ## [1.2.0] - 2025-01-01
36
+
37
+ - Initial published version with independent, master, and single switch modes
38
+ - Support for switch, outlet, lightbulb, and fan accessory types
39
+ - Per-switch config: type, defaultState, delayOff
40
+ - Homebridge UI config schema
package/README.md CHANGED
@@ -5,26 +5,26 @@
5
5
  [![GitHub issues](https://img.shields.io/github/issues/azadaydinli/homebridge-multiple-switch)](https://github.com/azadaydinli/homebridge-multiple-switch/issues)
6
6
  [![GitHub license](https://img.shields.io/github/license/azadaydinli/homebridge-multiple-switch)](https://github.com/azadaydinli/homebridge-multiple-switch/blob/master/LICENSE)
7
7
 
8
- A lightweight Homebridge plugin that lets you create multiple customizable dummy switches under a single accessory — configurable as `Switch`, `Outlet`, `Lightbulb`, or `Fan`.
8
+ A lightweight Homebridge plugin that lets you create multiple customizable dummy switches under a single accessory — configurable as `Switch`, `Outlet`, `Lightbulb`, or `Fan`.
9
9
  Supports `Independent`, `Master`, and `Single` switch modes.
10
10
 
11
11
  ---
12
12
 
13
- ## Features
13
+ ## Features
14
14
 
15
15
  - Grouped multiple switches in one HomeKit tile
16
16
  - Accessory type: `switch`, `outlet`, `lightbulb`, or `fan`
17
17
  - **Independent Mode** – All switches operate separately
18
- - **Master Mode** – One master switch controls the rest
18
+ - **Master Mode** – One switch controls all others
19
19
  - **Single Mode** – Only one switch can be active at a time
20
- - Per-switch config support (type, auto-off, default state)
21
- - Switch states are preserved after Homebridge restart
22
- - Fully dynamic config reload (no need to restart Homebridge)
20
+ - Per-switch config support (type, auto-off delay, default state)
21
+ - Switch states preserved across Homebridge restarts via cached accessories
22
+ - Automatic cleanup of stale accessories on config change
23
23
  - Compatible with HomeKit and Siri
24
24
 
25
25
  ---
26
26
 
27
- ## 📦 Installation
27
+ ## Installation
28
28
 
29
29
  Install via Homebridge UI:
30
30
 
@@ -40,20 +40,21 @@ npm install -g homebridge-multiple-switch
40
40
 
41
41
  ---
42
42
 
43
- ## ⚙️ Configuration (Platform Mode)
43
+ ## Configuration
44
44
 
45
- Configure from Homebridge UI or manually edit `config.json` like below:
45
+ Configure from Homebridge UI or manually edit `config.json`:
46
46
 
47
47
  ```json
48
48
  {
49
49
  "platform": "MultipleSwitchPlatform",
50
50
  "name": "Multiple Switches",
51
+ "switchBehavior": "single",
51
52
  "switches": [
52
53
  {
53
54
  "name": "Heater",
54
55
  "type": "outlet",
55
56
  "defaultState": true,
56
- "autoTurnOff": 10000
57
+ "delayOff": 10000
57
58
  },
58
59
  {
59
60
  "name": "Fan",
@@ -62,35 +63,34 @@ Configure from Homebridge UI or manually edit `config.json` like below:
62
63
  {
63
64
  "name": "Light",
64
65
  "type": "lightbulb",
65
- "autoTurnOff": 5000
66
+ "delayOff": 5000
66
67
  }
67
- ],
68
- "mode": "single"
68
+ ]
69
69
  }
70
70
  ```
71
71
 
72
72
  ---
73
73
 
74
- ### 🔧 Configuration Options
74
+ ### Platform Options
75
75
 
76
- | Field | Type | Required | Description |
77
- |----------------|---------|----------|-------------------------------------------------------------------------|
78
- | `name` | string | | Name of the platform instance |
79
- | `switches` | array | | List of switches to create |
80
- | `mode` | string | | `independent`, `master`, or `single` |
81
- | `type` | string | ❌ | Switch type: `switch`, `outlet`, `lightbulb`, `fan` (overridden per switch) |
82
- | `autoTurnOff` | number | ❌ | Global auto-off (ms) – can be overridden per switch |
83
- | `defaultState` | boolean | ❌ | Default power state on restart – can be overridden per switch |
76
+ | Field | Type | Required | Description |
77
+ |------------------|--------|----------|--------------------------------------|
78
+ | `name` | string | Yes | Name of the platform instance |
79
+ | `switchBehavior` | string | No | `independent`, `master`, or `single` |
80
+ | `switches` | array | Yes | List of switches to create |
84
81
 
85
- Each object inside `switches[]` can include:
86
- - `name`: Name of the switch
87
- - `type`: Optional (`switch`, `outlet`, etc.)
88
- - `autoTurnOff`: Optional (in ms)
89
- - `defaultState`: Optional (true/false)
82
+ ### Per-Switch Options
83
+
84
+ | Field | Type | Required | Description |
85
+ |----------------|---------|----------|--------------------------------------------------|
86
+ | `name` | string | Yes | Name of the switch |
87
+ | `type` | string | No | `switch`, `outlet`, `lightbulb`, or `fan` |
88
+ | `defaultState` | boolean | No | Initial power state (default: `false`) |
89
+ | `delayOff` | number | No | Auto turn off after N milliseconds (default: `0`) |
90
90
 
91
91
  ---
92
92
 
93
- ## 📣 Example Use Cases
93
+ ## Example Use Cases
94
94
 
95
95
  - Simulate smart plugs for automation testing
96
96
  - Trigger HomeKit scenes manually
@@ -99,7 +99,7 @@ Each object inside `switches[]` can include:
99
99
 
100
100
  ---
101
101
 
102
- ## 🔗 Links
102
+ ## Links
103
103
 
104
104
  - [NPM Package](https://www.npmjs.com/package/homebridge-multiple-switch)
105
105
  - [Homebridge](https://homebridge.io/)
@@ -107,6 +107,6 @@ Each object inside `switches[]` can include:
107
107
 
108
108
  ---
109
109
 
110
- ## 📜 License
110
+ ## License
111
111
 
112
112
  MIT © [Azad Aydınlı](https://github.com/azadaydinli)
@@ -7,11 +7,13 @@
7
7
  "properties": {
8
8
  "name": {
9
9
  "title": "Platform Name",
10
+ "description": "The name of this platform instance in HomeKit.",
10
11
  "type": "string",
11
12
  "default": "Multiple Switch Platform"
12
13
  },
13
14
  "switchBehavior": {
14
15
  "title": "Switch Behavior Mode",
16
+ "description": "Controls how switches interact with each other.",
15
17
  "type": "string",
16
18
  "default": "independent",
17
19
  "oneOf": [
@@ -22,16 +24,19 @@
22
24
  },
23
25
  "switches": {
24
26
  "title": "Switches",
27
+ "description": "List of virtual switches to create.",
25
28
  "type": "array",
26
29
  "items": {
27
30
  "type": "object",
28
31
  "properties": {
29
32
  "name": {
30
33
  "title": "Switch Name",
34
+ "description": "Display name of the switch in HomeKit.",
31
35
  "type": "string"
32
36
  },
33
37
  "type": {
34
38
  "title": "Switch Type",
39
+ "description": "The HomeKit accessory type for this switch.",
35
40
  "type": "string",
36
41
  "default": "outlet",
37
42
  "oneOf": [
@@ -43,11 +48,13 @@
43
48
  },
44
49
  "defaultState": {
45
50
  "title": "Default State",
51
+ "description": "Initial power state when Homebridge starts.",
46
52
  "type": "boolean",
47
53
  "default": false
48
54
  },
49
55
  "delayOff": {
50
56
  "title": "Auto Turn Off (ms)",
57
+ "description": "Automatically turn off after this many milliseconds. Set to 0 to disable.",
51
58
  "type": "number",
52
59
  "default": 0,
53
60
  "minimum": 0
@@ -67,4 +74,4 @@
67
74
  },
68
75
  "required": ["name", "switches"]
69
76
  }
70
- }
77
+ }
package/i18n/ar.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "headerDisplay": "منصة المفاتيح المتعددة",
3
+ "schema": {
4
+ "name": {
5
+ "title": "اسم المنصة",
6
+ "description": "اسم هذا المثيل من المنصة في HomeKit."
7
+ },
8
+ "switchBehavior": {
9
+ "title": "وضع سلوك المفتاح",
10
+ "description": "يتحكم في كيفية تفاعل المفاتيح مع بعضها البعض.",
11
+ "oneOf": [
12
+ { "title": "مستقل" },
13
+ { "title": "رئيسي" },
14
+ { "title": "فردي" }
15
+ ]
16
+ },
17
+ "switches": {
18
+ "title": "المفاتيح",
19
+ "description": "قائمة المفاتيح الافتراضية المراد إنشاؤها.",
20
+ "items": {
21
+ "name": {
22
+ "title": "اسم المفتاح",
23
+ "description": "اسم العرض للمفتاح في HomeKit."
24
+ },
25
+ "type": {
26
+ "title": "نوع المفتاح",
27
+ "description": "نوع ملحق HomeKit لهذا المفتاح.",
28
+ "oneOf": [
29
+ { "title": "مفتاح" },
30
+ { "title": "مقبس" },
31
+ { "title": "مصباح" },
32
+ { "title": "مروحة" }
33
+ ]
34
+ },
35
+ "defaultState": {
36
+ "title": "الحالة الافتراضية",
37
+ "description": "حالة الطاقة الأولية عند بدء تشغيل Homebridge."
38
+ },
39
+ "delayOff": {
40
+ "title": "إيقاف تلقائي (مللي ثانية)",
41
+ "description": "يتم الإيقاف تلقائيًا بعد هذا العدد من المللي ثانية. اضبط على 0 للتعطيل."
42
+ }
43
+ }
44
+ }
45
+ }
46
+ }
package/i18n/de.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "headerDisplay": "Mehrfachschalter-Plattform",
3
+ "schema": {
4
+ "name": {
5
+ "title": "Plattformname",
6
+ "description": "Der Name dieser Plattforminstanz in HomeKit."
7
+ },
8
+ "switchBehavior": {
9
+ "title": "Schaltverhalten",
10
+ "description": "Steuert, wie die Schalter miteinander interagieren.",
11
+ "oneOf": [
12
+ { "title": "Unabhängig" },
13
+ { "title": "Master" },
14
+ { "title": "Einzeln" }
15
+ ]
16
+ },
17
+ "switches": {
18
+ "title": "Schalter",
19
+ "description": "Liste der zu erstellenden virtuellen Schalter.",
20
+ "items": {
21
+ "name": {
22
+ "title": "Schaltername",
23
+ "description": "Anzeigename des Schalters in HomeKit."
24
+ },
25
+ "type": {
26
+ "title": "Schaltertyp",
27
+ "description": "Der HomeKit-Zubehörtyp für diesen Schalter.",
28
+ "oneOf": [
29
+ { "title": "Schalter" },
30
+ { "title": "Steckdose" },
31
+ { "title": "Glühbirne" },
32
+ { "title": "Ventilator" }
33
+ ]
34
+ },
35
+ "defaultState": {
36
+ "title": "Standardzustand",
37
+ "description": "Anfänglicher Energiezustand beim Start von Homebridge."
38
+ },
39
+ "delayOff": {
40
+ "title": "Automatisch ausschalten (ms)",
41
+ "description": "Schaltet nach dieser Anzahl von Millisekunden automatisch aus. Zum Deaktivieren auf 0 setzen."
42
+ }
43
+ }
44
+ }
45
+ }
46
+ }
package/i18n/en.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "headerDisplay": "Multiple Switch Platform",
3
+ "schema": {
4
+ "name": {
5
+ "title": "Platform Name",
6
+ "description": "The name of this platform instance in HomeKit."
7
+ },
8
+ "switchBehavior": {
9
+ "title": "Switch Behavior Mode",
10
+ "description": "Controls how switches interact with each other.",
11
+ "oneOf": [
12
+ { "title": "Independent" },
13
+ { "title": "Master" },
14
+ { "title": "Single" }
15
+ ]
16
+ },
17
+ "switches": {
18
+ "title": "Switches",
19
+ "description": "List of virtual switches to create.",
20
+ "items": {
21
+ "name": {
22
+ "title": "Switch Name",
23
+ "description": "Display name of the switch in HomeKit."
24
+ },
25
+ "type": {
26
+ "title": "Switch Type",
27
+ "description": "The HomeKit accessory type for this switch.",
28
+ "oneOf": [
29
+ { "title": "Switch" },
30
+ { "title": "Outlet" },
31
+ { "title": "Lightbulb" },
32
+ { "title": "Fan" }
33
+ ]
34
+ },
35
+ "defaultState": {
36
+ "title": "Default State",
37
+ "description": "Initial power state when Homebridge starts."
38
+ },
39
+ "delayOff": {
40
+ "title": "Auto Turn Off (ms)",
41
+ "description": "Automatically turn off after this many milliseconds. Set to 0 to disable."
42
+ }
43
+ }
44
+ }
45
+ }
46
+ }
package/i18n/es.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "headerDisplay": "Plataforma de Múltiples Interruptores",
3
+ "schema": {
4
+ "name": {
5
+ "title": "Nombre de la plataforma",
6
+ "description": "El nombre de esta instancia de plataforma en HomeKit."
7
+ },
8
+ "switchBehavior": {
9
+ "title": "Modo de comportamiento",
10
+ "description": "Controla cómo interactúan los interruptores entre sí.",
11
+ "oneOf": [
12
+ { "title": "Independiente" },
13
+ { "title": "Maestro" },
14
+ { "title": "Único" }
15
+ ]
16
+ },
17
+ "switches": {
18
+ "title": "Interruptores",
19
+ "description": "Lista de interruptores virtuales a crear.",
20
+ "items": {
21
+ "name": {
22
+ "title": "Nombre del interruptor",
23
+ "description": "Nombre visible del interruptor en HomeKit."
24
+ },
25
+ "type": {
26
+ "title": "Tipo de interruptor",
27
+ "description": "El tipo de accesorio HomeKit para este interruptor.",
28
+ "oneOf": [
29
+ { "title": "Interruptor" },
30
+ { "title": "Enchufe" },
31
+ { "title": "Bombilla" },
32
+ { "title": "Ventilador" }
33
+ ]
34
+ },
35
+ "defaultState": {
36
+ "title": "Estado predeterminado",
37
+ "description": "Estado de energía inicial cuando Homebridge se inicia."
38
+ },
39
+ "delayOff": {
40
+ "title": "Apagado automático (ms)",
41
+ "description": "Se apaga automáticamente después de estos milisegundos. Establecer en 0 para desactivar."
42
+ }
43
+ }
44
+ }
45
+ }
46
+ }
package/i18n/fr.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "headerDisplay": "Plateforme Multi-Interrupteurs",
3
+ "schema": {
4
+ "name": {
5
+ "title": "Nom de la plateforme",
6
+ "description": "Le nom de cette instance de plateforme dans HomeKit."
7
+ },
8
+ "switchBehavior": {
9
+ "title": "Mode de comportement",
10
+ "description": "Contrôle la façon dont les interrupteurs interagissent entre eux.",
11
+ "oneOf": [
12
+ { "title": "Indépendant" },
13
+ { "title": "Maître" },
14
+ { "title": "Unique" }
15
+ ]
16
+ },
17
+ "switches": {
18
+ "title": "Interrupteurs",
19
+ "description": "Liste des interrupteurs virtuels à créer.",
20
+ "items": {
21
+ "name": {
22
+ "title": "Nom de l'interrupteur",
23
+ "description": "Nom affiché de l'interrupteur dans HomeKit."
24
+ },
25
+ "type": {
26
+ "title": "Type d'interrupteur",
27
+ "description": "Le type d'accessoire HomeKit pour cet interrupteur.",
28
+ "oneOf": [
29
+ { "title": "Interrupteur" },
30
+ { "title": "Prise" },
31
+ { "title": "Ampoule" },
32
+ { "title": "Ventilateur" }
33
+ ]
34
+ },
35
+ "defaultState": {
36
+ "title": "État par défaut",
37
+ "description": "État d'alimentation initial au démarrage de Homebridge."
38
+ },
39
+ "delayOff": {
40
+ "title": "Arrêt automatique (ms)",
41
+ "description": "S'éteint automatiquement après ce nombre de millisecondes. Mettre à 0 pour désactiver."
42
+ }
43
+ }
44
+ }
45
+ }
46
+ }
package/i18n/it.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "headerDisplay": "Piattaforma Multi-Interruttore",
3
+ "schema": {
4
+ "name": {
5
+ "title": "Nome della piattaforma",
6
+ "description": "Il nome di questa istanza della piattaforma in HomeKit."
7
+ },
8
+ "switchBehavior": {
9
+ "title": "Modalità di comportamento",
10
+ "description": "Controlla come gli interruttori interagiscono tra loro.",
11
+ "oneOf": [
12
+ { "title": "Indipendente" },
13
+ { "title": "Master" },
14
+ { "title": "Singolo" }
15
+ ]
16
+ },
17
+ "switches": {
18
+ "title": "Interruttori",
19
+ "description": "Elenco degli interruttori virtuali da creare.",
20
+ "items": {
21
+ "name": {
22
+ "title": "Nome dell'interruttore",
23
+ "description": "Nome visualizzato dell'interruttore in HomeKit."
24
+ },
25
+ "type": {
26
+ "title": "Tipo di interruttore",
27
+ "description": "Il tipo di accessorio HomeKit per questo interruttore.",
28
+ "oneOf": [
29
+ { "title": "Interruttore" },
30
+ { "title": "Presa" },
31
+ { "title": "Lampadina" },
32
+ { "title": "Ventilatore" }
33
+ ]
34
+ },
35
+ "defaultState": {
36
+ "title": "Stato predefinito",
37
+ "description": "Stato di alimentazione iniziale all'avvio di Homebridge."
38
+ },
39
+ "delayOff": {
40
+ "title": "Spegnimento automatico (ms)",
41
+ "description": "Si spegne automaticamente dopo questi millisecondi. Impostare a 0 per disattivare."
42
+ }
43
+ }
44
+ }
45
+ }
46
+ }
package/i18n/ja.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "headerDisplay": "マルチスイッチプラットフォーム",
3
+ "schema": {
4
+ "name": {
5
+ "title": "プラットフォーム名",
6
+ "description": "HomeKit でのこのプラットフォームインスタンスの名前。"
7
+ },
8
+ "switchBehavior": {
9
+ "title": "スイッチ動作モード",
10
+ "description": "スイッチ同士の相互作用を制御します。",
11
+ "oneOf": [
12
+ { "title": "独立" },
13
+ { "title": "マスター" },
14
+ { "title": "シングル" }
15
+ ]
16
+ },
17
+ "switches": {
18
+ "title": "スイッチ",
19
+ "description": "作成する仮想スイッチのリスト。",
20
+ "items": {
21
+ "name": {
22
+ "title": "スイッチ名",
23
+ "description": "HomeKit でのスイッチの表示名。"
24
+ },
25
+ "type": {
26
+ "title": "スイッチタイプ",
27
+ "description": "このスイッチの HomeKit アクセサリタイプ。",
28
+ "oneOf": [
29
+ { "title": "スイッチ" },
30
+ { "title": "コンセント" },
31
+ { "title": "電球" },
32
+ { "title": "ファン" }
33
+ ]
34
+ },
35
+ "defaultState": {
36
+ "title": "デフォルト状態",
37
+ "description": "Homebridge 起動時の初期電源状態。"
38
+ },
39
+ "delayOff": {
40
+ "title": "自動オフ(ミリ秒)",
41
+ "description": "指定したミリ秒後に自動的にオフになります。無効にするには 0 に設定してください。"
42
+ }
43
+ }
44
+ }
45
+ }
46
+ }
package/i18n/ko.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "headerDisplay": "멀티 스위치 플랫폼",
3
+ "schema": {
4
+ "name": {
5
+ "title": "플랫폼 이름",
6
+ "description": "HomeKit에서 이 플랫폼 인스턴스의 이름."
7
+ },
8
+ "switchBehavior": {
9
+ "title": "스위치 동작 모드",
10
+ "description": "스위치 간의 상호 작용 방식을 제어합니다.",
11
+ "oneOf": [
12
+ { "title": "독립" },
13
+ { "title": "마스터" },
14
+ { "title": "단일" }
15
+ ]
16
+ },
17
+ "switches": {
18
+ "title": "스위치",
19
+ "description": "생성할 가상 스위치 목록.",
20
+ "items": {
21
+ "name": {
22
+ "title": "스위치 이름",
23
+ "description": "HomeKit에서 스위치의 표시 이름."
24
+ },
25
+ "type": {
26
+ "title": "스위치 유형",
27
+ "description": "이 스위치의 HomeKit 액세서리 유형.",
28
+ "oneOf": [
29
+ { "title": "스위치" },
30
+ { "title": "콘센트" },
31
+ { "title": "전구" },
32
+ { "title": "팬" }
33
+ ]
34
+ },
35
+ "defaultState": {
36
+ "title": "기본 상태",
37
+ "description": "Homebridge 시작 시 초기 전원 상태."
38
+ },
39
+ "delayOff": {
40
+ "title": "자동 꺼짐 (ms)",
41
+ "description": "지정된 밀리초 후 자동으로 꺼집니다. 비활성화하려면 0으로 설정하세요."
42
+ }
43
+ }
44
+ }
45
+ }
46
+ }
package/i18n/nl.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "headerDisplay": "Meervoudige schakelaar platform",
3
+ "schema": {
4
+ "name": {
5
+ "title": "Platformnaam",
6
+ "description": "De naam van dit platformexemplaar in HomeKit."
7
+ },
8
+ "switchBehavior": {
9
+ "title": "Schakelaargedrag",
10
+ "description": "Bepaalt hoe de schakelaars met elkaar omgaan.",
11
+ "oneOf": [
12
+ { "title": "Onafhankelijk" },
13
+ { "title": "Master" },
14
+ { "title": "Enkelvoudig" }
15
+ ]
16
+ },
17
+ "switches": {
18
+ "title": "Schakelaars",
19
+ "description": "Lijst van virtuele schakelaars om aan te maken.",
20
+ "items": {
21
+ "name": {
22
+ "title": "Schakelaarnaam",
23
+ "description": "Weergavenaam van de schakelaar in HomeKit."
24
+ },
25
+ "type": {
26
+ "title": "Schakelaartype",
27
+ "description": "Het HomeKit-accessoiretype voor deze schakelaar.",
28
+ "oneOf": [
29
+ { "title": "Schakelaar" },
30
+ { "title": "Stopcontact" },
31
+ { "title": "Gloeilamp" },
32
+ { "title": "Ventilator" }
33
+ ]
34
+ },
35
+ "defaultState": {
36
+ "title": "Standaardstatus",
37
+ "description": "Initiële voedingsstatus wanneer Homebridge opstart."
38
+ },
39
+ "delayOff": {
40
+ "title": "Automatisch uitschakelen (ms)",
41
+ "description": "Schakelt automatisch uit na dit aantal milliseconden. Stel in op 0 om uit te schakelen."
42
+ }
43
+ }
44
+ }
45
+ }
46
+ }
package/i18n/pl.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "headerDisplay": "Platforma wielu przełączników",
3
+ "schema": {
4
+ "name": {
5
+ "title": "Nazwa platformy",
6
+ "description": "Nazwa tej instancji platformy w HomeKit."
7
+ },
8
+ "switchBehavior": {
9
+ "title": "Tryb zachowania przełączników",
10
+ "description": "Kontroluje sposób interakcji przełączników ze sobą.",
11
+ "oneOf": [
12
+ { "title": "Niezależny" },
13
+ { "title": "Główny" },
14
+ { "title": "Pojedynczy" }
15
+ ]
16
+ },
17
+ "switches": {
18
+ "title": "Przełączniki",
19
+ "description": "Lista wirtualnych przełączników do utworzenia.",
20
+ "items": {
21
+ "name": {
22
+ "title": "Nazwa przełącznika",
23
+ "description": "Wyświetlana nazwa przełącznika w HomeKit."
24
+ },
25
+ "type": {
26
+ "title": "Typ przełącznika",
27
+ "description": "Typ akcesorium HomeKit dla tego przełącznika.",
28
+ "oneOf": [
29
+ { "title": "Przełącznik" },
30
+ { "title": "Gniazdko" },
31
+ { "title": "Żarówka" },
32
+ { "title": "Wentylator" }
33
+ ]
34
+ },
35
+ "defaultState": {
36
+ "title": "Stan domyślny",
37
+ "description": "Początkowy stan zasilania przy uruchomieniu Homebridge."
38
+ },
39
+ "delayOff": {
40
+ "title": "Automatyczne wyłączenie (ms)",
41
+ "description": "Automatycznie wyłącza się po podanej liczbie milisekund. Ustaw 0, aby wyłączyć."
42
+ }
43
+ }
44
+ }
45
+ }
46
+ }
package/i18n/pt.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "headerDisplay": "Plataforma de Múltiplos Interruptores",
3
+ "schema": {
4
+ "name": {
5
+ "title": "Nome da plataforma",
6
+ "description": "O nome desta instância de plataforma no HomeKit."
7
+ },
8
+ "switchBehavior": {
9
+ "title": "Modo de comportamento",
10
+ "description": "Controla como os interruptores interagem entre si.",
11
+ "oneOf": [
12
+ { "title": "Independente" },
13
+ { "title": "Mestre" },
14
+ { "title": "Único" }
15
+ ]
16
+ },
17
+ "switches": {
18
+ "title": "Interruptores",
19
+ "description": "Lista de interruptores virtuais a criar.",
20
+ "items": {
21
+ "name": {
22
+ "title": "Nome do interruptor",
23
+ "description": "Nome de exibição do interruptor no HomeKit."
24
+ },
25
+ "type": {
26
+ "title": "Tipo de interruptor",
27
+ "description": "O tipo de acessório HomeKit para este interruptor.",
28
+ "oneOf": [
29
+ { "title": "Interruptor" },
30
+ { "title": "Tomada" },
31
+ { "title": "Lâmpada" },
32
+ { "title": "Ventilador" }
33
+ ]
34
+ },
35
+ "defaultState": {
36
+ "title": "Estado padrão",
37
+ "description": "Estado de energia inicial quando o Homebridge inicia."
38
+ },
39
+ "delayOff": {
40
+ "title": "Desligamento automático (ms)",
41
+ "description": "Desliga automaticamente após estes milissegundos. Defina como 0 para desativar."
42
+ }
43
+ }
44
+ }
45
+ }
46
+ }
package/i18n/ru.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "headerDisplay": "Платформа мультипереключателей",
3
+ "schema": {
4
+ "name": {
5
+ "title": "Название платформы",
6
+ "description": "Название этого экземпляра платформы в HomeKit."
7
+ },
8
+ "switchBehavior": {
9
+ "title": "Режим поведения",
10
+ "description": "Управляет взаимодействием переключателей друг с другом.",
11
+ "oneOf": [
12
+ { "title": "Независимый" },
13
+ { "title": "Мастер" },
14
+ { "title": "Одиночный" }
15
+ ]
16
+ },
17
+ "switches": {
18
+ "title": "Переключатели",
19
+ "description": "Список виртуальных переключателей для создания.",
20
+ "items": {
21
+ "name": {
22
+ "title": "Название переключателя",
23
+ "description": "Отображаемое имя переключателя в HomeKit."
24
+ },
25
+ "type": {
26
+ "title": "Тип переключателя",
27
+ "description": "Тип аксессуара HomeKit для этого переключателя.",
28
+ "oneOf": [
29
+ { "title": "Выключатель" },
30
+ { "title": "Розетка" },
31
+ { "title": "Лампочка" },
32
+ { "title": "Вентилятор" }
33
+ ]
34
+ },
35
+ "defaultState": {
36
+ "title": "Состояние по умолчанию",
37
+ "description": "Начальное состояние питания при запуске Homebridge."
38
+ },
39
+ "delayOff": {
40
+ "title": "Автоматическое выключение (мс)",
41
+ "description": "Автоматически выключается через указанное количество миллисекунд. Установите 0 для отключения."
42
+ }
43
+ }
44
+ }
45
+ }
46
+ }
package/i18n/tr.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "headerDisplay": "Çoklu Anahtar Platformu",
3
+ "schema": {
4
+ "name": {
5
+ "title": "Platform Adı",
6
+ "description": "Bu platform örneğinin HomeKit'teki adı."
7
+ },
8
+ "switchBehavior": {
9
+ "title": "Anahtar Davranış Modu",
10
+ "description": "Anahtarların birbirleriyle nasıl etkileşime gireceğini kontrol eder.",
11
+ "oneOf": [
12
+ { "title": "Bağımsız" },
13
+ { "title": "Ana Kontrol" },
14
+ { "title": "Tekli" }
15
+ ]
16
+ },
17
+ "switches": {
18
+ "title": "Anahtarlar",
19
+ "description": "Oluşturulacak sanal anahtarların listesi.",
20
+ "items": {
21
+ "name": {
22
+ "title": "Anahtar Adı",
23
+ "description": "Anahtarın HomeKit'teki görünen adı."
24
+ },
25
+ "type": {
26
+ "title": "Anahtar Tipi",
27
+ "description": "Bu anahtar için HomeKit aksesuar tipi.",
28
+ "oneOf": [
29
+ { "title": "Anahtar" },
30
+ { "title": "Priz" },
31
+ { "title": "Ampul" },
32
+ { "title": "Vantilatör" }
33
+ ]
34
+ },
35
+ "defaultState": {
36
+ "title": "Varsayılan Durum",
37
+ "description": "Homebridge başladığında başlangıç güç durumu."
38
+ },
39
+ "delayOff": {
40
+ "title": "Otomatik Kapanma (ms)",
41
+ "description": "Belirtilen milisaniye sonra otomatik kapanır. Devre dışı bırakmak için 0 yazın."
42
+ }
43
+ }
44
+ }
45
+ }
46
+ }
@@ -0,0 +1,46 @@
1
+ {
2
+ "headerDisplay": "多开关平台",
3
+ "schema": {
4
+ "name": {
5
+ "title": "平台名称",
6
+ "description": "此平台实例在 HomeKit 中的名称。"
7
+ },
8
+ "switchBehavior": {
9
+ "title": "开关行为模式",
10
+ "description": "控制开关之间的交互方式。",
11
+ "oneOf": [
12
+ { "title": "独立" },
13
+ { "title": "主控" },
14
+ { "title": "单选" }
15
+ ]
16
+ },
17
+ "switches": {
18
+ "title": "开关",
19
+ "description": "要创建的虚拟开关列表。",
20
+ "items": {
21
+ "name": {
22
+ "title": "开关名称",
23
+ "description": "开关在 HomeKit 中的显示名称。"
24
+ },
25
+ "type": {
26
+ "title": "开关类型",
27
+ "description": "此开关的 HomeKit 配件类型。",
28
+ "oneOf": [
29
+ { "title": "开关" },
30
+ { "title": "插座" },
31
+ { "title": "灯泡" },
32
+ { "title": "风扇" }
33
+ ]
34
+ },
35
+ "defaultState": {
36
+ "title": "默认状态",
37
+ "description": "Homebridge 启动时的初始电源状态。"
38
+ },
39
+ "delayOff": {
40
+ "title": "自动关闭(毫秒)",
41
+ "description": "在指定的毫秒数后自动关闭。设置为 0 以禁用。"
42
+ }
43
+ }
44
+ }
45
+ }
46
+ }
package/index.js CHANGED
@@ -1,109 +1,160 @@
1
- // homebridge-multiple-switch: index.js (Platform plugin, bir Accessory, çox Service)
1
+ 'use strict';
2
2
 
3
- let Service, Characteristic, UUIDGen;
3
+ const PLUGIN_NAME = 'homebridge-multiple-switch';
4
+ const PLATFORM_NAME = 'MultipleSwitchPlatform';
4
5
 
5
- module.exports = (api) => {
6
- Service = api.hap.Service;
7
- Characteristic = api.hap.Characteristic;
8
- UUIDGen = api.hap.uuid;
6
+ const SERVICE_TYPES = {
7
+ switch: 'Switch',
8
+ lightbulb: 'Lightbulb',
9
+ fan: 'Fan',
10
+ outlet: 'Outlet',
11
+ };
9
12
 
10
- api.registerPlatform('MultipleSwitchPlatform', MultipleSwitchPlatform);
13
+ module.exports = (api) => {
14
+ api.registerPlatform(PLATFORM_NAME, MultipleSwitchPlatform);
11
15
  };
12
16
 
13
17
  class MultipleSwitchPlatform {
14
18
  constructor(log, config, api) {
15
19
  this.log = log;
16
- this.config = config;
20
+ this.config = config || {};
17
21
  this.api = api;
18
- this.accessories = [];
22
+ this.Service = api.hap.Service;
23
+ this.Characteristic = api.hap.Characteristic;
24
+ this.cachedAccessories = new Map();
25
+ this.switchServices = new Map();
19
26
 
20
27
  this.api.on('didFinishLaunching', () => {
21
- this.log('🔌 MultipleSwitchPlatform başladıldı.');
28
+ this.log.info('MultipleSwitchPlatform started.');
22
29
  this.setupAccessories();
23
30
  });
24
31
  }
25
32
 
33
+ configureAccessory(accessory) {
34
+ this.cachedAccessories.set(accessory.UUID, accessory);
35
+ }
36
+
26
37
  setupAccessories() {
27
- const switches = this.config.switches || [];
28
- const behavior = this.config.switchBehavior || 'independent';
38
+ const switches = this.config.switches;
39
+ if (!Array.isArray(switches) || switches.length === 0) {
40
+ this.log.warn('No switches configured. Removing stale accessories.');
41
+ this.removeStaleCachedAccessories();
42
+ return;
43
+ }
44
+
29
45
  const name = this.config.name || 'Multiple Switch Panel';
46
+ const behavior = this.config.switchBehavior || 'independent';
47
+ const uuid = this.api.hap.uuid.generate(name);
48
+
49
+ let accessory = this.cachedAccessories.get(uuid);
50
+ const isNew = !accessory;
30
51
 
31
- const uuid = UUIDGen.generate(name);
32
- const accessory = new this.api.platformAccessory(name, uuid);
52
+ if (isNew) {
53
+ accessory = new this.api.platformAccessory(name, uuid);
54
+ }
33
55
 
34
- accessory.context.switchStates = {};
35
- accessory.context.switchServices = {};
36
56
  accessory.context.switchBehavior = behavior;
57
+ accessory.context.switchStates = accessory.context.switchStates || {};
58
+
59
+ this.reconcileServices(accessory, switches);
60
+
61
+ if (isNew) {
62
+ this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
63
+ }
64
+
65
+ this.cachedAccessories.delete(uuid);
66
+ this.removeStaleCachedAccessories();
67
+ }
68
+
69
+ reconcileServices(accessory, switches) {
70
+ const activeSubtypes = new Set();
37
71
 
38
72
  switches.forEach((sw, index) => {
39
- const id = `switch_${index}`;
40
- const service = this.createSwitchService(accessory, sw, id);
73
+ const subtype = `switch_${index}`;
74
+ activeSubtypes.add(subtype);
41
75
 
42
- accessory.addService(service);
43
- accessory.context.switchStates[id] = sw.defaultState || false;
44
- accessory.context.switchServices[id] = service;
76
+ const ServiceClass = this.getServiceClass(sw.type);
77
+ let service = accessory.getServiceById(ServiceClass, subtype);
78
+
79
+ if (!service) {
80
+ service = accessory.addService(ServiceClass, sw.name, subtype);
81
+ }
82
+
83
+ service.setCharacteristic(this.Characteristic.Name, sw.name);
84
+ this.configureSwitchHandlers(accessory, service, sw, subtype);
85
+
86
+ this.switchServices.set(subtype, service);
87
+
88
+ if (accessory.context.switchStates[subtype] === undefined) {
89
+ accessory.context.switchStates[subtype] = sw.defaultState || false;
90
+ }
45
91
  });
46
92
 
47
- this.api.registerPlatformAccessories(
48
- 'homebridge-multiple-switch',
49
- 'MultipleSwitchPlatform',
50
- [accessory]
51
- );
52
- this.accessories.push(accessory);
93
+ const servicesToRemove = accessory.services.filter((s) => {
94
+ return s.subtype && !activeSubtypes.has(s.subtype);
95
+ });
96
+ servicesToRemove.forEach((s) => accessory.removeService(s));
53
97
  }
54
98
 
55
- createSwitchService(accessory, sw, id) {
56
- const ServiceType = this.getServiceClass(sw.type);
57
- const service = new ServiceType(sw.name, id);
58
-
59
- service.getCharacteristic(Characteristic.On)
60
- .onGet(() => {
61
- return accessory.context.switchStates[id];
62
- })
99
+ configureSwitchHandlers(accessory, service, sw, subtype) {
100
+ service.getCharacteristic(this.Characteristic.On)
101
+ .onGet(() => accessory.context.switchStates[subtype] ?? false)
63
102
  .onSet((value) => {
103
+ accessory.context.switchStates[subtype] = value;
104
+ this.log.info(`[${sw.name}] ${value ? 'ON' : 'OFF'}`);
105
+
64
106
  const behavior = accessory.context.switchBehavior;
65
- accessory.context.switchStates[id] = value;
66
- this.log(`[${sw.name}] → ${value ? 'ON' : 'OFF'}`);
67
107
 
68
108
  if (behavior === 'single' && value) {
69
- Object.keys(accessory.context.switchStates).forEach(key => {
70
- if (key !== id) {
71
- accessory.context.switchStates[key] = false;
72
- accessory.context.switchServices[key].updateCharacteristic(Characteristic.On, false);
73
- }
74
- });
109
+ this.turnOffOthers(accessory, subtype);
75
110
  }
76
111
 
77
112
  if (behavior === 'master') {
78
- Object.keys(accessory.context.switchStates).forEach(key => {
79
- accessory.context.switchStates[key] = value;
80
- accessory.context.switchServices[key].updateCharacteristic(Characteristic.On, value);
81
- });
82
- } else {
83
- if (value && sw.delayOff > 0) {
84
- setTimeout(() => {
85
- accessory.context.switchStates[id] = false;
86
- service.updateCharacteristic(Characteristic.On, false);
87
- this.log(`[${sw.name}] auto-off after ${sw.delayOff}ms`);
88
- }, sw.delayOff);
89
- }
113
+ this.setAll(accessory, value);
114
+ }
115
+
116
+ if (value && sw.delayOff > 0) {
117
+ this.scheduleAutoOff(accessory, service, sw, subtype);
90
118
  }
91
119
  });
120
+ }
92
121
 
93
- return service;
122
+ turnOffOthers(accessory, excludeSubtype) {
123
+ for (const [key, svc] of this.switchServices) {
124
+ if (key !== excludeSubtype) {
125
+ accessory.context.switchStates[key] = false;
126
+ svc.updateCharacteristic(this.Characteristic.On, false);
127
+ }
128
+ }
94
129
  }
95
130
 
96
- getServiceClass(type) {
97
- switch ((type || '').toLowerCase()) {
98
- case 'switch': return Service.Switch;
99
- case 'lightbulb': return Service.Lightbulb;
100
- case 'fan': return Service.Fan;
101
- case 'outlet':
102
- default: return Service.Outlet;
131
+ setAll(accessory, value) {
132
+ for (const [key, svc] of this.switchServices) {
133
+ accessory.context.switchStates[key] = value;
134
+ svc.updateCharacteristic(this.Characteristic.On, value);
103
135
  }
104
136
  }
105
137
 
106
- configureAccessory(accessory) {
107
- this.accessories.push(accessory);
138
+ scheduleAutoOff(accessory, service, sw, subtype) {
139
+ setTimeout(() => {
140
+ if (accessory.context.switchStates[subtype]) {
141
+ accessory.context.switchStates[subtype] = false;
142
+ service.updateCharacteristic(this.Characteristic.On, false);
143
+ this.log.info(`[${sw.name}] auto-off after ${sw.delayOff}ms`);
144
+ }
145
+ }, sw.delayOff);
146
+ }
147
+
148
+ getServiceClass(type) {
149
+ const key = (type || 'outlet').toLowerCase();
150
+ const name = SERVICE_TYPES[key] || SERVICE_TYPES.outlet;
151
+ return this.Service[name];
152
+ }
153
+
154
+ removeStaleCachedAccessories() {
155
+ if (this.cachedAccessories.size === 0) return;
156
+ const stale = [...this.cachedAccessories.values()];
157
+ this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, stale);
158
+ this.cachedAccessories.clear();
108
159
  }
109
160
  }
package/package.json CHANGED
@@ -1,11 +1,15 @@
1
1
  {
2
2
  "name": "homebridge-multiple-switch",
3
- "version": "1.2.0",
3
+ "version": "1.3.1",
4
4
  "description": "Multiple switch platform for Homebridge",
5
5
  "homepage": "https://github.com/azadaydinli/homebridge-multiple-switch",
6
6
  "main": "index.js",
7
7
  "author": "Azad Aydınlı",
8
8
  "license": "ISC",
9
+ "scripts": {
10
+ "lint": "echo \"No linter configured\"",
11
+ "test": "echo \"No tests configured\""
12
+ },
9
13
  "bugs": {
10
14
  "url": "https://github.com/azadaydinli/homebridge-multiple-switch/issues"
11
15
  },
@@ -26,7 +30,7 @@
26
30
  "url": "https://github.com/azadaydinli/homebridge-multiple-switch.git"
27
31
  },
28
32
  "engines": {
29
- "node": ">=14.17.0",
33
+ "node": ">=18.0.0",
30
34
  "homebridge": ">=1.3.0"
31
35
  }
32
36
  }
@@ -1,28 +0,0 @@
1
- name: 📦 Publish to npm
2
-
3
- on:
4
- push:
5
- tags:
6
- - 'v*.*.*'
7
-
8
- jobs:
9
- publish:
10
- runs-on: ubuntu-latest
11
-
12
- steps:
13
- - name: 📥 Repo-nu klonla
14
- uses: actions/checkout@v3
15
-
16
- - name: ⚙️ Node.js qur
17
- uses: actions/setup-node@v3
18
- with:
19
- node-version: '18'
20
- registry-url: 'https://registry.npmjs.org/'
21
-
22
- - name: 📦 Asılılıqları qur
23
- run: npm install
24
-
25
- - name: 🚀 NPM-ə yüklə
26
- run: npm publish
27
- env:
28
- NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
@@ -1,28 +0,0 @@
1
- name: 📦 Publish to npm
2
-
3
- on:
4
- push:
5
- tags:
6
- - 'v*.*.*'
7
-
8
- jobs:
9
- publish:
10
- runs-on: ubuntu-latest
11
-
12
- steps:
13
- - name: 📥 Repo-nu klonla
14
- uses: actions/checkout@v3
15
-
16
- - name: ⚙️ Node.js qur
17
- uses: actions/setup-node@v3
18
- with:
19
- node-version: '18'
20
- registry-url: 'https://registry.npmjs.org/'
21
-
22
- - name: 📦 Asılılıqları qur
23
- run: npm install
24
-
25
- - name: 🚀 NPM-ə yüklə
26
- run: npm publish
27
- env:
28
- NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
@@ -1,28 +0,0 @@
1
- name: 📦 Publish to npm
2
-
3
- on:
4
- push:
5
- tags:
6
- - 'v*.*.*'
7
-
8
- jobs:
9
- publish:
10
- runs-on: ubuntu-latest
11
-
12
- steps:
13
- - name: 📥 Repo-nu klonla
14
- uses: actions/checkout@v3
15
-
16
- - name: ⚙️ Node.js qur
17
- uses: actions/setup-node@v3
18
- with:
19
- node-version: '18'
20
- registry-url: 'https://registry.npmjs.org/'
21
-
22
- - name: 📦 Asılılıqları qur
23
- run: npm install
24
-
25
- - name: 🚀 NPM-ə yüklə
26
- run: npm publish
27
- env:
28
- NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}