OverviewFilesCommitsIssuesDocsReleasesPeers

pear-git-deploy commits / 42f4283

Add deploy config management, logging, and status
- DeployConfig: per-repo config CRUD (enabled, mode, channel, autoDeploy) - DeployLog: deploy history with append/getLog/getLatest/clear - getDeployStatus(): assembles status from config + latest log entry - Pipeline auto-logs success/failure after each deploy - 10 tests, 32 assertions Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
42f42837e4a107f74de4454c6054e2617441d608
by plankatron <fleeky@gmail.com> · 2026-03-30T17:32:42.000Z

Changed files (6) +907 -3

index.js +1 -0
……@@ -1 +1,2 @@
11export { DeployPipeline, OTAUpdater, rollback } from './lib/pipeline.js'
2export { DeployConfig, DeployLog, getDeployStatus } from './lib/config.js'
lib/config.js +136 -0
……@@ -0,0 +1,136 @@
1/**
2 * DeployConfig — Per-repo deploy configuration stored in Hyperbee
3 *
4 * Config fields per repo:
5 * enabled: boolean
6 * channel: string (target channel/branch)
7 * mode: 'git' | 'legacy'
8 * autoRelease: boolean
9 * autoSeed: boolean
10 * autoDeploy: boolean (trigger deploy on push)
11 * configuredAt: timestamp
12 */
13
14export class DeployConfig {
15 constructor (bee) {
16 this._bee = bee
17 }
18
19 async get (repoName) {
20 const entry = await this._bee.get(`deploy-config:${repoName}`)
21 return entry ? entry.value : null
22 }
23
24 async set (repoName, config) {
25 const existing = await this.get(repoName) || {}
26 const merged = {
27 ...existing,
28 ...config,
29 repoName,
30 configuredAt: Date.now()
31 }
32 await this._bee.put(`deploy-config:${repoName}`, merged)
33 return merged
34 }
35
36 async delete (repoName) {
37 await this._bee.del(`deploy-config:${repoName}`)
38 }
39
40 async list () {
41 const configs = []
42 for await (const entry of this._bee.createReadStream({
43 gt: 'deploy-config:', lt: 'deploy-config:\xff'
44 })) {
45 configs.push(entry.value)
46 }
47 return configs
48 }
49
50 async isEnabled (repoName) {
51 const config = await this.get(repoName)
52 return config ? config.enabled === true : false
53 }
54
55 async shouldAutoDeploy (repoName) {
56 const config = await this.get(repoName)
57 return config ? config.autoDeploy === true : false
58 }
59}
60
61/**
62 * DeployLog — Deploy history per repo stored in Hyperbee
63 *
64 * Each entry: { timestamp, status, commitHash, duration, error?, message? }
65 */
66export class DeployLog {
67 constructor (bee) {
68 this._bee = bee
69 }
70
71 async append (repoName, entry) {
72 const key = `deploy-log:${repoName}:${Date.now()}`
73 await this._bee.put(key, {
74 ...entry,
75 repoName,
76 timestamp: Date.now()
77 })
78 }
79
80 async getLog (repoName, limit = 20) {
81 const entries = []
82 for await (const entry of this._bee.createReadStream({
83 gt: `deploy-log:${repoName}:`, lt: `deploy-log:${repoName}:\xff`,
84 reverse: true, limit
85 })) {
86 entries.push(entry.value)
87 }
88 return entries
89 }
90
91 async getLatest (repoName) {
92 for await (const entry of this._bee.createReadStream({
93 gt: `deploy-log:${repoName}:`, lt: `deploy-log:${repoName}:\xff`,
94 reverse: true, limit: 1
95 })) {
96 return entry.value
97 }
98 return null
99 }
100
101 async clear (repoName) {
102 const keys = []
103 for await (const entry of this._bee.createReadStream({
104 gt: `deploy-log:${repoName}:`, lt: `deploy-log:${repoName}:\xff`
105 })) {
106 keys.push(entry.key)
107 }
108 for (const key of keys) await this._bee.del(key)
109 }
110}
111
112/**
113 * getDeployStatus — assemble status from config + log
114 */
115export async function getDeployStatus (config, log, repoName) {
116 const cfg = await config.get(repoName)
117 const latest = await log.getLatest(repoName)
118
119 return {
120 repoName,
121 configured: !!cfg,
122 enabled: cfg?.enabled || false,
123 mode: cfg?.mode || 'git',
124 channel: cfg?.channel || 'release',
125 autoDeploy: cfg?.autoDeploy || false,
126 autoRelease: cfg?.autoRelease || false,
127 autoSeed: cfg?.autoSeed || false,
128 lastDeploy: latest ? {
129 timestamp: latest.timestamp,
130 status: latest.status,
131 commitHash: latest.commitHash,
132 duration: latest.duration,
133 error: latest.error || null
134 } : null
135 }
136}
lib/pipeline.js +13 -1
……@@ -19,6 +19,7 @@ export class DeployPipeline {
1919 this.storagePath = opts.storagePath
2020 this.reposPath = opts.reposPath
2121 this.gitSync = opts.gitSync || null
22 this.deployLog = opts.deployLog || null
2223 this._locks = new Map() // repoName → Promise
2324 }
2425
……@@ -79,13 +80,24 @@ export class DeployPipeline {
7980
8081 console.log(`[Deploy] Complete: ${repoName} ${commitHash.slice(0, 8)} in ${duration}ms`)
8182
82 // 4. Broadcast ref-update
83 // 4. Log the deploy
84 if (this.deployLog) {
85 await this.deployLog.append(repoName, { status: 'success', commitHash, duration })
86 }
87
88 // 5. Broadcast ref-update
8389 if (this.gitSync) {
8490 const refs = { 'refs/heads/release': commitHash }
8591 this.gitSync.broadcastRefUpdate(repoName, refs)
8692 }
8793
8894 return { repoName, commitHash, duration }
95 } catch (err) {
96 // Log failure
97 if (this.deployLog) {
98 await this.deployLog.append(repoName, { status: 'failed', error: err.message, duration: Date.now() - timestamp })
99 }
100 throw err
89101 } finally {
90102 // Clean up temp dir
91103 try { await fs.promises.rm(checkoutDir, { recursive: true }) } catch {}
package-lock.json +578 -1
……@@ -8,7 +8,27 @@
88 "name": "pear-git-deploy",
99 "version": "1.0.0",
1010 "devDependencies": {
11 "brittle": "^3.0.0"
11 "brittle": "^3.19.1",
12 "corestore": "^7.9.2",
13 "hyperbee": "^2.27.3"
14 }
15 },
16 "node_modules/@hyperswarm/secret-stream": {
17 "version": "6.9.1",
18 "resolved": "https://registry.npmjs.org/@hyperswarm/secret-stream/-/secret-stream-6.9.1.tgz",
19 "integrity": "sha512-xb0S5y3YJwBakD77JOGBHlBxdp63mHClZoXBYoLv+9wH8e054ESKlmQptWqjJK5dv5VMUIVYOJB4MaOpB0JdGw==",
20 "dev": true,
21 "license": "Apache-2.0",
22 "dependencies": {
23 "b4a": "^1.1.0",
24 "hypercore-crypto": "^3.3.1",
25 "noise-curve-ed": "^2.0.1",
26 "noise-handshake": "^4.0.0",
27 "sodium-secretstream": "^1.1.0",
28 "sodium-universal": "^5.0.0",
29 "streamx": "^2.14.0",
30 "timeout-refresh": "^2.0.0",
31 "unslab": "^1.3.0"
1232 }
1333 },
1434 "node_modules/@jridgewell/resolve-uri": {
……@@ -688,6 +708,13 @@
688708 }
689709 }
690710 },
711 "node_modules/big-sparse-array": {
712 "version": "1.0.3",
713 "resolved": "https://registry.npmjs.org/big-sparse-array/-/big-sparse-array-1.0.3.tgz",
714 "integrity": "sha512-6RjV/3mSZORlMdpUaQ6rUSpG637cZm0//E54YYGtQg1c1O+AbZP8UTdJ/TchsDZcTVLmyWZcseBfp2HBeXUXOQ==",
715 "dev": true,
716 "license": "MIT"
717 },
691718 "node_modules/brittle": {
692719 "version": "3.19.1",
693720 "resolved": "https://registry.npmjs.org/brittle/-/brittle-3.19.1.tgz",
……@@ -718,6 +745,26 @@
718745 "brittle-pear": "bin/pear.js"
719746 }
720747 },
748 "node_modules/codecs": {
749 "version": "3.1.0",
750 "resolved": "https://registry.npmjs.org/codecs/-/codecs-3.1.0.tgz",
751 "integrity": "sha512-Dqx8NwvBvnMeuPQdVKy/XEF71igjR5apxBvCGeV0pP1tXadOiaLvDTXt7xh+/5wI1ASB195mXQGJbw3Ml4YDWQ==",
752 "dev": true,
753 "license": "MIT",
754 "dependencies": {
755 "b4a": "^1.6.3"
756 }
757 },
758 "node_modules/compact-encoding": {
759 "version": "2.19.2",
760 "resolved": "https://registry.npmjs.org/compact-encoding/-/compact-encoding-2.19.2.tgz",
761 "integrity": "sha512-/YjhHQE/5L4F7l5Bht69dRbP9RV6zoJPeowi8bMKQxNKe3Nh6hOY8pBGoVE9fz5GaWfEd8fWJ2aU9sB4KZuMYg==",
762 "dev": true,
763 "license": "Apache-2.0",
764 "dependencies": {
765 "b4a": "^1.3.0"
766 }
767 },
721768 "node_modules/convert-source-map": {
722769 "version": "2.0.0",
723770 "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
……@@ -725,6 +772,45 @@
725772 "dev": true,
726773 "license": "MIT"
727774 },
775 "node_modules/corestore": {
776 "version": "7.9.2",
777 "resolved": "https://registry.npmjs.org/corestore/-/corestore-7.9.2.tgz",
778 "integrity": "sha512-dyIktVSVLu0skZ1UM4yODbNbZ46dBpTHypzwlwET1btVuS3YIf66Zz8Tx8h3Xa8nbLO2USGqrYtvPYzSRj2wCA==",
779 "dev": true,
780 "license": "MIT",
781 "dependencies": {
782 "b4a": "^1.6.7",
783 "hypercore": "^11.19.0",
784 "hypercore-crypto": "^3.4.2",
785 "hypercore-errors": "^1.4.0",
786 "hypercore-id-encoding": "^1.3.0",
787 "ready-resource": "^1.1.1",
788 "sodium-universal": "^5.0.1",
789 "which-runtime": "^1.2.1"
790 }
791 },
792 "node_modules/debounceify": {
793 "version": "1.1.0",
794 "resolved": "https://registry.npmjs.org/debounceify/-/debounceify-1.1.0.tgz",
795 "integrity": "sha512-eKuHDVfJVg+u/0nPy8P+fhnLgbyuTgVxuCRrS/R7EpDSMMkBDgSes41MJtSAY1F1hcqfHz3Zy/qpqHHIp/EhdA==",
796 "dev": true,
797 "license": "MIT"
798 },
799 "node_modules/device-file": {
800 "version": "2.3.1",
801 "resolved": "https://registry.npmjs.org/device-file/-/device-file-2.3.1.tgz",
802 "integrity": "sha512-bmON44lwxJPle9N2OcH4tqM44pMGZKT8G6OkzXkz0urvqQ9LKkoQdTS6w0ztYfmLdUE27R4UUmx0+y2gEz4Jug==",
803 "dev": true,
804 "license": "Apache-2.0",
805 "dependencies": {
806 "b4a": "^1.6.7",
807 "bare-fs": "^4.0.1",
808 "bare-path": "^3.0.0",
809 "fd-lock": "^2.1.0",
810 "fs-native-extensions": "^1.4.0",
811 "ready-resource": "^1.2.0"
812 }
813 },
728814 "node_modules/error-stack-parser": {
729815 "version": "2.1.4",
730816 "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz",
……@@ -752,6 +838,54 @@
752838 "dev": true,
753839 "license": "MIT"
754840 },
841 "node_modules/fd-lock": {
842 "version": "2.1.1",
843 "resolved": "https://registry.npmjs.org/fd-lock/-/fd-lock-2.1.1.tgz",
844 "integrity": "sha512-H3VkcWFl39Rk0xBokDcUyBcVs6VrYTUo/DhDWMzJOF99wXWDAvmvq/GlLTS2NTehsznZhp3fXVyrtB2ldDXhwg==",
845 "dev": true,
846 "license": "Apache-2.0",
847 "dependencies": {
848 "bare-fs": "^4.5.0",
849 "fs-native-extensions": "^1.4.4",
850 "ready-resource": "^1.2.0",
851 "resource-on-exit": "^1.0.0"
852 }
853 },
854 "node_modules/flat-tree": {
855 "version": "1.13.0",
856 "resolved": "https://registry.npmjs.org/flat-tree/-/flat-tree-1.13.0.tgz",
857 "integrity": "sha512-fT3HIuCPwHhFgJ20QYzDHgUG0zMmFg5cHvFiFo5h+QMSJ28TihsEVY0f8HGliuO+pOzmvjMx1odToeaEWkTnyQ==",
858 "dev": true,
859 "license": "MIT"
860 },
861 "node_modules/fs-native-extensions": {
862 "version": "1.4.5",
863 "resolved": "https://registry.npmjs.org/fs-native-extensions/-/fs-native-extensions-1.4.5.tgz",
864 "integrity": "sha512-ekV0T//iDm4AvhOcuPaHpxub4DI7HvY5ucLJVDvi7T2J+NZkQ9S6MuvgP0yeQvoqNUaAGyLjVYb1905BF9bpmg==",
865 "dev": true,
866 "license": "Apache-2.0",
867 "dependencies": {
868 "require-addon": "^1.1.0",
869 "which-runtime": "^1.2.0"
870 }
871 },
872 "node_modules/generate-object-property": {
873 "version": "2.0.0",
874 "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-2.0.0.tgz",
875 "integrity": "sha512-KwuURPyqn2Mz8DdV29pJwQu0Y7tcsbkULr82eeOcY/ZllFK6I9Wm8dsRByIu7CKWlFi9BdW1b3mcXMp/kQBQsw==",
876 "dev": true,
877 "license": "MIT",
878 "dependencies": {
879 "is-property": "^1.0.0"
880 }
881 },
882 "node_modules/generate-string": {
883 "version": "1.0.1",
884 "resolved": "https://registry.npmjs.org/generate-string/-/generate-string-1.0.1.tgz",
885 "integrity": "sha512-IfTY0dKZM43ACyGvXkbG7De7WY7MxTS5VO6Juhe8oJKpCmrYYXoqp/cJMskkpi0k9H8wuXq0H+eI898/BCqvXg==",
886 "dev": true,
887 "license": "MIT"
888 },
755889 "node_modules/globbie": {
756890 "version": "1.0.3",
757891 "resolved": "https://registry.npmjs.org/globbie/-/globbie-1.0.3.tgz",
……@@ -765,6 +899,193 @@
765899 "picomatch": "^4.0.2"
766900 }
767901 },
902 "node_modules/hyperbee": {
903 "version": "2.27.3",
904 "resolved": "https://registry.npmjs.org/hyperbee/-/hyperbee-2.27.3.tgz",
905 "integrity": "sha512-PXURH2U4juUZyJRKHTrY5z1zX851pmI1Q0jfv5F/hCIErDt/ND8jOZuxc3hfOLM9f0W3qJEDTMlV5AJBkVPy8w==",
906 "dev": true,
907 "license": "MIT",
908 "dependencies": {
909 "b4a": "^1.6.0",
910 "codecs": "^3.0.0",
911 "debounceify": "^1.0.0",
912 "hypercore-errors": "^1.0.0",
913 "mutexify": "^1.4.0",
914 "protocol-buffers-encodings": "^1.2.0",
915 "rache": "^1.0.0",
916 "ready-resource": "^1.0.0",
917 "resolve-reject-promise": "^1.1.0",
918 "safety-catch": "^1.0.2",
919 "streamx": "^2.12.4",
920 "unslab": "^1.2.0"
921 }
922 },
923 "node_modules/hypercore": {
924 "version": "11.28.1",
925 "resolved": "https://registry.npmjs.org/hypercore/-/hypercore-11.28.1.tgz",
926 "integrity": "sha512-d8jqUilG7Gri8wT+sRdkFTYRPMHGyvBz/NCYHf5rX7+LoivRNRxQm5nWChT7caiUKbcsDak6h7IiEQ5wbD1wCg==",
927 "dev": true,
928 "license": "MIT",
929 "dependencies": {
930 "@hyperswarm/secret-stream": "^6.0.0",
931 "b4a": "^1.1.0",
932 "bare-events": "^2.2.0",
933 "big-sparse-array": "^1.0.3",
934 "compact-encoding": "^2.11.0",
935 "fast-fifo": "^1.3.0",
936 "flat-tree": "^1.9.0",
937 "hypercore-crypto": "^3.2.1",
938 "hypercore-errors": "^1.5.0",
939 "hypercore-id-encoding": "^1.2.0",
940 "hypercore-storage": "^2.8.0",
941 "is-options": "^1.0.1",
942 "nanoassert": "^2.0.0",
943 "protomux": "^3.5.0",
944 "quickbit-universal": "^2.2.0",
945 "random-array-iterator": "^1.0.0",
946 "safety-catch": "^1.0.1",
947 "sodium-universal": "^5.0.1",
948 "streamx": "^2.12.4",
949 "unslab": "^1.3.0",
950 "z32": "^1.0.0"
951 }
952 },
953 "node_modules/hypercore-crypto": {
954 "version": "3.6.1",
955 "resolved": "https://registry.npmjs.org/hypercore-crypto/-/hypercore-crypto-3.6.1.tgz",
956 "integrity": "sha512-ltIz2uDwy9pO/ZGTvqcjzyBkvt6O4cVm4r/nNxh0GFs/RbQtqP/i4wCvLEdmU7ptgtnw7fI67WYD1aHPuv4OVA==",
957 "dev": true,
958 "license": "MIT",
959 "dependencies": {
960 "b4a": "^1.6.6",
961 "compact-encoding": "^2.15.0",
962 "sodium-universal": "^5.0.0"
963 }
964 },
965 "node_modules/hypercore-errors": {
966 "version": "1.5.0",
967 "resolved": "https://registry.npmjs.org/hypercore-errors/-/hypercore-errors-1.5.0.tgz",
968 "integrity": "sha512-5KQ/SuDxsvet+7qWA35Ay6zdD9WyAHQoyWHGcPUTbmJBd300gvNIJoi3oma7kp4TTCSzii6qYumNZe/s0j/saQ==",
969 "dev": true,
970 "license": "Apache-2.0",
971 "dependencies": {
972 "hypercore-id-encoding": "^1.3.0"
973 }
974 },
975 "node_modules/hypercore-id-encoding": {
976 "version": "1.3.0",
977 "resolved": "https://registry.npmjs.org/hypercore-id-encoding/-/hypercore-id-encoding-1.3.0.tgz",
978 "integrity": "sha512-W6sHdGo5h7LXEsoWfKf/KfuROZmZRQDlGqJF2EPHW+noCK66Vvr0+zE6cL0vqQi18s0kQPeN7Sq3QyR0Ytc2VQ==",
979 "dev": true,
980 "license": "Apache-2.0",
981 "dependencies": {
982 "b4a": "^1.5.3",
983 "z32": "^1.0.0"
984 }
985 },
986 "node_modules/hypercore-storage": {
987 "version": "2.8.0",
988 "resolved": "https://registry.npmjs.org/hypercore-storage/-/hypercore-storage-2.8.0.tgz",
989 "integrity": "sha512-s35HllFMYvrkSHvkIIklVFmCI/bRVNO7H3pi4mKvWl0XLyhGGGM+NtJ7fpsh82oPRCmL9XtMRO+pT6LiMAO8MA==",
990 "dev": true,
991 "license": "Apache-2.0",
992 "dependencies": {
993 "b4a": "^1.6.7",
994 "bare-fs": "^4.0.1",
995 "bare-path": "^3.0.0",
996 "compact-encoding": "^2.16.0",
997 "device-file": "^2.1.2",
998 "flat-tree": "^1.12.1",
999 "hypercore-crypto": "^3.4.2",
1000 "hyperschema": "^1.7.0",
1001 "index-encoder": "^3.3.2",
1002 "resolve-reject-promise": "^1.0.0",
1003 "rocksdb-native": "^3.11.0",
1004 "scope-lock": "^1.2.4",
1005 "streamx": "^2.21.1"
1006 }
1007 },
1008 "node_modules/hyperschema": {
1009 "version": "1.20.1",
1010 "resolved": "https://registry.npmjs.org/hyperschema/-/hyperschema-1.20.1.tgz",
1011 "integrity": "sha512-7gnvaTNUs8FqdrU3NNE6nzhOneGyDlUlpROu9YFlHA61fE8ppKKf2gLZjZjsx/enNt+VFN4ITxy8ijypfyWBLw==",
1012 "dev": true,
1013 "license": "Apache-2.0",
1014 "dependencies": {
1015 "bare-fs": "^4.0.1",
1016 "compact-encoding": "^2.19.0",
1017 "generate-object-property": "^2.0.0",
1018 "generate-string": "^1.0.1"
1019 }
1020 },
1021 "node_modules/index-encoder": {
1022 "version": "3.5.0",
1023 "resolved": "https://registry.npmjs.org/index-encoder/-/index-encoder-3.5.0.tgz",
1024 "integrity": "sha512-idZ1cxtZz2dRV6rUiaP9Xo99UjXbSzjcMacoQmxUMu/A7fEQcNPngvwDJYeWelQUS5XFlY71/or70lKn6XnwbQ==",
1025 "dev": true,
1026 "license": "Apache-2.0",
1027 "dependencies": {
1028 "b4a": "^1.6.4"
1029 }
1030 },
1031 "node_modules/is-options": {
1032 "version": "1.0.2",
1033 "resolved": "https://registry.npmjs.org/is-options/-/is-options-1.0.2.tgz",
1034 "integrity": "sha512-u+Ai74c8Q74aS8BuHwPdI1jptGOT1FQXgCq8/zv0xRuE+wRgSMEJLj8lVO8Zp9BeGb29BXY6AsNPinfqjkr7Fg==",
1035 "dev": true,
1036 "license": "MIT",
1037 "dependencies": {
1038 "b4a": "^1.1.1"
1039 }
1040 },
1041 "node_modules/is-property": {
1042 "version": "1.0.2",
1043 "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
1044 "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==",
1045 "dev": true,
1046 "license": "MIT"
1047 },
1048 "node_modules/mutexify": {
1049 "version": "1.4.0",
1050 "resolved": "https://registry.npmjs.org/mutexify/-/mutexify-1.4.0.tgz",
1051 "integrity": "sha512-pbYSsOrSB/AKN5h/WzzLRMFgZhClWccf2XIB4RSMC8JbquiB0e0/SH5AIfdQMdyHmYtv4seU7yV/TvAwPLJ1Yg==",
1052 "dev": true,
1053 "license": "MIT",
1054 "dependencies": {
1055 "queue-tick": "^1.0.0"
1056 }
1057 },
1058 "node_modules/nanoassert": {
1059 "version": "2.0.0",
1060 "resolved": "https://registry.npmjs.org/nanoassert/-/nanoassert-2.0.0.tgz",
1061 "integrity": "sha512-7vO7n28+aYO4J+8w96AzhmU8G+Y/xpPDJz/se19ICsqj/momRbb9mh9ZUtkoJ5X3nTnPdhEJyc0qnM6yAsHBaA==",
1062 "dev": true,
1063 "license": "ISC"
1064 },
1065 "node_modules/noise-curve-ed": {
1066 "version": "2.1.0",
1067 "resolved": "https://registry.npmjs.org/noise-curve-ed/-/noise-curve-ed-2.1.0.tgz",
1068 "integrity": "sha512-zAzJx+VwZM3w6EA1hTmDhJfvAnCeBQn/1FAeZ0LtGxCcCtlAK/uJXQVF/eDVUOaAZ286lHlx77WJ+qj9SmsRRg==",
1069 "dev": true,
1070 "license": "ISC",
1071 "dependencies": {
1072 "b4a": "^1.1.0",
1073 "nanoassert": "^2.0.0",
1074 "sodium-universal": "^5.0.0"
1075 }
1076 },
1077 "node_modules/noise-handshake": {
1078 "version": "4.2.0",
1079 "resolved": "https://registry.npmjs.org/noise-handshake/-/noise-handshake-4.2.0.tgz",
1080 "integrity": "sha512-9O/VTNX/E2/AToyMTTDU0J/4WhaXMTdqc2DHs9vf+snoZ0cenSBq0dNYTVV1snYYEkmo6QeRrYMxtqtoYnY+LA==",
1081 "dev": true,
1082 "license": "Apache-2.0",
1083 "dependencies": {
1084 "b4a": "^1.1.0",
1085 "nanoassert": "^2.0.0",
1086 "sodium-universal": "^5.0.0"
1087 }
1088 },
7681089 "node_modules/paparam": {
7691090 "version": "1.10.1",
7701091 "resolved": "https://registry.npmjs.org/paparam/-/paparam-1.10.1.tgz",
……@@ -785,6 +1106,95 @@
7851106 "url": "https://github.com/sponsors/jonschlinkert"
7861107 }
7871108 },
1109 "node_modules/protocol-buffers-encodings": {
1110 "version": "1.2.0",
1111 "resolved": "https://registry.npmjs.org/protocol-buffers-encodings/-/protocol-buffers-encodings-1.2.0.tgz",
1112 "integrity": "sha512-daeNPuKh1NlLD1uDfbLpD+xyUTc07nEtfHwmBZmt/vH0B7VOM+JOCOpDcx9ZRpqHjAiIkGqyTDi+wfGSl17R9w==",
1113 "dev": true,
1114 "license": "MIT",
1115 "dependencies": {
1116 "b4a": "^1.6.0",
1117 "signed-varint": "^2.0.1",
1118 "varint": "5.0.0"
1119 }
1120 },
1121 "node_modules/protomux": {
1122 "version": "3.10.1",
1123 "resolved": "https://registry.npmjs.org/protomux/-/protomux-3.10.1.tgz",
1124 "integrity": "sha512-jgBqx8ZyaBWea/DFG4eOu1scOaeBwcnagiRC1XFVrjeGt7oAb0Pk5udPpBUpJ4DJBRjra50jD6YcZiQQTRqaaA==",
1125 "dev": true,
1126 "license": "MIT",
1127 "dependencies": {
1128 "b4a": "^1.3.1",
1129 "compact-encoding": "^2.5.1",
1130 "queue-tick": "^1.0.0",
1131 "safety-catch": "^1.0.1",
1132 "unslab": "^1.3.0"
1133 }
1134 },
1135 "node_modules/queue-tick": {
1136 "version": "1.0.1",
1137 "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz",
1138 "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==",
1139 "dev": true,
1140 "license": "MIT"
1141 },
1142 "node_modules/quickbit-native": {
1143 "version": "2.4.8",
1144 "resolved": "https://registry.npmjs.org/quickbit-native/-/quickbit-native-2.4.8.tgz",
1145 "integrity": "sha512-FcCcqI+nIAWGknqhtrYT5TSD7t/N+Xd8ctM+2PrIIBuwOi5hx0SxAvuPtzLIEMfT/2h9+fhBakUe2uALOHX6yw==",
1146 "dev": true,
1147 "license": "Apache-2.0",
1148 "optional": true,
1149 "dependencies": {
1150 "require-addon": "^1.1.0"
1151 }
1152 },
1153 "node_modules/quickbit-universal": {
1154 "version": "2.2.0",
1155 "resolved": "https://registry.npmjs.org/quickbit-universal/-/quickbit-universal-2.2.0.tgz",
1156 "integrity": "sha512-w02i1R8n7+6pEKTud8DfF8zbFY9o7RtPlUc3jWbtCkDKvhbx/AvV7oNnz4/TcmsPGpSJS+fq5Ud6RH6+YPvSGg==",
1157 "dev": true,
1158 "license": "ISC",
1159 "dependencies": {
1160 "b4a": "^1.6.0",
1161 "simdle-universal": "^1.1.0"
1162 },
1163 "optionalDependencies": {
1164 "quickbit-native": "^2.2.0"
1165 }
1166 },
1167 "node_modules/rache": {
1168 "version": "1.0.0",
1169 "resolved": "https://registry.npmjs.org/rache/-/rache-1.0.0.tgz",
1170 "integrity": "sha512-e0k0g0w/8jOCB+7YqCIlOa+OJ38k0wrYS4x18pMSmqOvLKoyhmMhmQyCcvfY6VaP8D75cqkEnlakXs+RYYLqNg==",
1171 "dev": true,
1172 "license": "Apache-2.0"
1173 },
1174 "node_modules/random-array-iterator": {
1175 "version": "1.0.0",
1176 "resolved": "https://registry.npmjs.org/random-array-iterator/-/random-array-iterator-1.0.0.tgz",
1177 "integrity": "sha512-u7xCM93XqKEvPTP6xZp2ehttcAemKnh73oKNf1FvzuVCfpt6dILDt1Kxl1LeBjm2iNIeR49VGFhy4Iz3yOun+Q==",
1178 "dev": true,
1179 "license": "MIT"
1180 },
1181 "node_modules/ready-resource": {
1182 "version": "1.2.0",
1183 "resolved": "https://registry.npmjs.org/ready-resource/-/ready-resource-1.2.0.tgz",
1184 "integrity": "sha512-nfcco/8iAFV0M+2PYnmIc+/xY0iRb35d42HFHQ7AfjulbGEAFa+XWpByfwSyeVeiBoMLLFVMv1HixxNCqzSQ1g==",
1185 "dev": true,
1186 "license": "MIT",
1187 "dependencies": {
1188 "bare-events": "^2.2.0"
1189 }
1190 },
1191 "node_modules/refcounter": {
1192 "version": "1.0.0",
1193 "resolved": "https://registry.npmjs.org/refcounter/-/refcounter-1.0.0.tgz",
1194 "integrity": "sha512-1WosVzUy0kPUaPMEtlNDwm99UsteALIhXXR8rerELoa63WkYIXAl0hxgwPFrIYBRWZPGUyekQ04FRtPJ7dHk9w==",
1195 "dev": true,
1196 "license": "Apache-2.0"
1197 },
7881198 "node_modules/require-addon": {
7891199 "version": "1.2.0",
7901200 "resolved": "https://registry.npmjs.org/require-addon/-/require-addon-1.2.0.tgz",
……@@ -798,6 +1208,46 @@
7981208 "bare": ">=1.10.0"
7991209 }
8001210 },
1211 "node_modules/resolve-reject-promise": {
1212 "version": "1.1.0",
1213 "resolved": "https://registry.npmjs.org/resolve-reject-promise/-/resolve-reject-promise-1.1.0.tgz",
1214 "integrity": "sha512-LWsTOA91AqzBTjSGgX79Tc130pwcBK6xjpJEO+qRT5IKZ6bGnHKcc8QL3upUBcWuU8OTIDzKK2VNSwmmlqvAVg==",
1215 "dev": true,
1216 "license": "MIT"
1217 },
1218 "node_modules/resource-on-exit": {
1219 "version": "1.0.0",
1220 "resolved": "https://registry.npmjs.org/resource-on-exit/-/resource-on-exit-1.0.0.tgz",
1221 "integrity": "sha512-ViJwJAknCkLRJRPR+9SISQQ7R5eRgtdIHLJsM2hHx1MweAJbJxJ5XnMjjq0Lc7ZGv44ufzAqds1nKxiVkdy4ag==",
1222 "dev": true,
1223 "license": "Apache-2.0"
1224 },
1225 "node_modules/rocksdb-native": {
1226 "version": "3.15.0",
1227 "resolved": "https://registry.npmjs.org/rocksdb-native/-/rocksdb-native-3.15.0.tgz",
1228 "integrity": "sha512-czYx0hOzKAy+HYmJROZm7thvJ1tHFWHNkGI3nomumXWzrJghJdrWo4MIhehGX4OTff0lnOCoB2zu6ekzIKY0rQ==",
1229 "dev": true,
1230 "license": "Apache-2.0",
1231 "dependencies": {
1232 "compact-encoding": "^2.15.0",
1233 "ready-resource": "^1.0.0",
1234 "refcounter": "^1.0.0",
1235 "require-addon": "^1.0.2",
1236 "resolve-reject-promise": "^1.1.0",
1237 "signal-promise": "^1.0.3",
1238 "streamx": "^2.16.1"
1239 },
1240 "engines": {
1241 "bare": ">=1.16.0"
1242 }
1243 },
1244 "node_modules/safety-catch": {
1245 "version": "1.0.3",
1246 "resolved": "https://registry.npmjs.org/safety-catch/-/safety-catch-1.0.3.tgz",
1247 "integrity": "sha512-Zq+J1TefpoEq/HTUabo0YXX5MNvttjWYODGohgPBO2jfko8Wqx3JYMgE823szDFVamdH5PlpByvfiWScTdSYDA==",
1248 "dev": true,
1249 "license": "MIT"
1250 },
8011251 "node_modules/same-object": {
8021252 "version": "1.0.2",
8031253 "resolved": "https://registry.npmjs.org/same-object/-/same-object-1.0.2.tgz",
……@@ -805,6 +1255,99 @@
8051255 "dev": true,
8061256 "license": "MIT"
8071257 },
1258 "node_modules/scope-lock": {
1259 "version": "1.2.4",
1260 "resolved": "https://registry.npmjs.org/scope-lock/-/scope-lock-1.2.4.tgz",
1261 "integrity": "sha512-BpSd8VCuCxW9ZitcdIC/vjs3gMaP9bRBL5nkHcyfX2VrS52n13/rHuBA2xJ/S/4DPuRdAO/Bk8pWd8eD/gHCIA==",
1262 "dev": true,
1263 "license": "Apache-2.0"
1264 },
1265 "node_modules/signal-promise": {
1266 "version": "1.0.3",
1267 "resolved": "https://registry.npmjs.org/signal-promise/-/signal-promise-1.0.3.tgz",
1268 "integrity": "sha512-WBgv0UnIq2C+Aeh0/n+IRpP6967eIx9WpynTUoiW3isPpfe1zu2LJzyfXdo9Tgef8yR/sGjcMvoUXD7EYdiz+g==",
1269 "dev": true,
1270 "license": "MIT"
1271 },
1272 "node_modules/signed-varint": {
1273 "version": "2.0.1",
1274 "resolved": "https://registry.npmjs.org/signed-varint/-/signed-varint-2.0.1.tgz",
1275 "integrity": "sha512-abgDPg1106vuZZOvw7cFwdCABddfJRz5akcCcchzTbhyhYnsG31y4AlZEgp315T7W3nQq5P4xeOm186ZiPVFzw==",
1276 "dev": true,
1277 "license": "MIT",
1278 "dependencies": {
1279 "varint": "~5.0.0"
1280 }
1281 },
1282 "node_modules/simdle-native": {
1283 "version": "1.3.9",
1284 "resolved": "https://registry.npmjs.org/simdle-native/-/simdle-native-1.3.9.tgz",
1285 "integrity": "sha512-Isc8sP4OiiIU0mpslD4GHEnR0VQWvR/54WN7YtwEDkNdTJVWtpmvsSvsgRlw5BNGxdYXlVRegdnrSu10H/PhvA==",
1286 "dev": true,
1287 "license": "Apache-2.0",
1288 "optional": true,
1289 "dependencies": {
1290 "b4a": "^1.6.0",
1291 "require-addon": "^1.1.0"
1292 }
1293 },
1294 "node_modules/simdle-universal": {
1295 "version": "1.1.2",
1296 "resolved": "https://registry.npmjs.org/simdle-universal/-/simdle-universal-1.1.2.tgz",
1297 "integrity": "sha512-3n3w1bs+uwgHKQjt6arez83EywNlhZzYvNOhvAASTl/8KqNIcqr6aHyGt3JRlfuUC7iB0tomJRPlJ2cRGIpBzA==",
1298 "dev": true,
1299 "license": "ISC",
1300 "dependencies": {
1301 "b4a": "^1.6.0"
1302 },
1303 "optionalDependencies": {
1304 "simdle-native": "^1.1.1"
1305 }
1306 },
1307 "node_modules/sodium-native": {
1308 "version": "5.1.0",
1309 "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-5.1.0.tgz",
1310 "integrity": "sha512-3RxgyWyJlhTsABPnJVpCI5CoTDANZTqqFrEPqr+kjfnRaBihpVtMUE3yTF40ukdoB1APXeoBNKF3MzZAIHg39g==",
1311 "dev": true,
1312 "license": "MIT",
1313 "dependencies": {
1314 "bare-assert": "^1.2.0",
1315 "require-addon": "^1.1.0",
1316 "which-runtime": "^1.2.1"
1317 },
1318 "engines": {
1319 "bare": ">=1.16.0"
1320 }
1321 },
1322 "node_modules/sodium-secretstream": {
1323 "version": "1.2.0",
1324 "resolved": "https://registry.npmjs.org/sodium-secretstream/-/sodium-secretstream-1.2.0.tgz",
1325 "integrity": "sha512-q/DbraNFXm1KfCiiZvapmz5UC3OlpirYFIvBK2MhGaOFSb3gRyk8OXTi17UI9SGfshQNCpsVvlopogbzZNyW6Q==",
1326 "dev": true,
1327 "license": "MIT",
1328 "dependencies": {
1329 "b4a": "^1.1.1",
1330 "sodium-universal": "^5.0.0"
1331 }
1332 },
1333 "node_modules/sodium-universal": {
1334 "version": "5.0.1",
1335 "resolved": "https://registry.npmjs.org/sodium-universal/-/sodium-universal-5.0.1.tgz",
1336 "integrity": "sha512-rv+aH+tnKB5H0MAc2UadHShLMslpJsc4wjdnHRtiSIEYpOetCgu8MS4ExQRia+GL/MK3uuCyZPeEsi+J3h+Q+Q==",
1337 "dev": true,
1338 "license": "MIT",
1339 "dependencies": {
1340 "sodium-native": "^5.0.1"
1341 },
1342 "peerDependencies": {
1343 "sodium-javascript": "~0.8.0"
1344 },
1345 "peerDependenciesMeta": {
1346 "sodium-javascript": {
1347 "optional": true
1348 }
1349 }
1350 },
8081351 "node_modules/stackframe": {
8091352 "version": "1.3.4",
8101353 "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz",
……@@ -856,6 +1399,13 @@
8561399 "b4a": "^1.6.4"
8571400 }
8581401 },
1402 "node_modules/timeout-refresh": {
1403 "version": "2.0.1",
1404 "resolved": "https://registry.npmjs.org/timeout-refresh/-/timeout-refresh-2.0.1.tgz",
1405 "integrity": "sha512-SVqEcMZBsZF9mA78rjzCrYrUs37LMJk3ShZ851ygZYW1cMeIjs9mL57KO6Iv5mmjSQnOe/29/VAfGXo+oRCiVw==",
1406 "dev": true,
1407 "license": "MIT"
1408 },
8591409 "node_modules/tmatch": {
8601410 "version": "5.0.0",
8611411 "resolved": "https://registry.npmjs.org/tmatch/-/tmatch-5.0.0.tgz",
……@@ -866,6 +1416,16 @@
8661416 "node": ">=8"
8671417 }
8681418 },
1419 "node_modules/unslab": {
1420 "version": "1.3.0",
1421 "resolved": "https://registry.npmjs.org/unslab/-/unslab-1.3.0.tgz",
1422 "integrity": "sha512-YATkfKAFj47kTzmiQrWXMyRvaVrHsW6MEALa4bm+FhiA2YG4oira+Z3DXN6LrYOYn2Y8eO94Lwl9DOHjs1FpoQ==",
1423 "dev": true,
1424 "license": "Apache-2.0",
1425 "dependencies": {
1426 "b4a": "^1.6.6"
1427 }
1428 },
8691429 "node_modules/v8-to-istanbul": {
8701430 "version": "9.3.0",
8711431 "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",
……@@ -881,12 +1441,29 @@
8811441 "node": ">=10.12.0"
8821442 }
8831443 },
1444 "node_modules/varint": {
1445 "version": "5.0.0",
1446 "resolved": "https://registry.npmjs.org/varint/-/varint-5.0.0.tgz",
1447 "integrity": "sha512-gC13b/bWrqQoKY2EmROCZ+AR0jitc6DnDGaQ6Ls9QpKmuSgJB1eQ7H3KETtQm7qSdMWMKCmsshyCmUwMLh3OAA==",
1448 "dev": true,
1449 "license": "MIT"
1450 },
8841451 "node_modules/which-runtime": {
8851452 "version": "1.3.2",
8861453 "resolved": "https://registry.npmjs.org/which-runtime/-/which-runtime-1.3.2.tgz",
8871454 "integrity": "sha512-5kwCfWml7+b2NO7KrLMhYihjRx0teKkd3yGp1Xk5Vaf2JGdSh+rgVhEALAD9c/59dP+YwJHXoEO7e8QPy7gOkw==",
8881455 "dev": true,
8891456 "license": "Apache-2.0"
1457 },
1458 "node_modules/z32": {
1459 "version": "1.1.0",
1460 "resolved": "https://registry.npmjs.org/z32/-/z32-1.1.0.tgz",
1461 "integrity": "sha512-1WUHy+VS6d0HPNspDxvLssBbeQjXMjSnpv0vH82vRAUfg847NmX3OXozp/hRP5jPhxBbrVzrgvAt+UsGNzRFQQ==",
1462 "dev": true,
1463 "license": "MIT",
1464 "dependencies": {
1465 "b4a": "^1.5.3"
1466 }
8901467 }
8911468 }
8921469}
package.json +3 -1
……@@ -5,6 +5,8 @@
55 "type": "module",
66 "main": "index.js",
77 "devDependencies": {
8 "brittle": "^3.0.0"
8 "brittle": "^3.19.1",
9 "corestore": "^7.9.2",
10 "hyperbee": "^2.27.3"
911 }
1012}
test/config.test.js +176 -0
……@@ -0,0 +1,176 @@
1import test from 'brittle'
2import Corestore from 'corestore'
3import Hyperbee from 'hyperbee'
4import { mkdtempSync } from 'fs'
5import { join } from 'path'
6import { tmpdir } from 'os'
7import { DeployConfig, DeployLog, getDeployStatus } from '../lib/config.js'
8
9function tmpBee () {
10 const store = new Corestore(mkdtempSync(join(tmpdir(), 'deploy-config-')))
11 const bee = new Hyperbee(store.get({ name: 'deploy' }), { keyEncoding: 'utf-8', valueEncoding: 'json' })
12 return bee
13}
14
15// ── DeployConfig ─────────────────────────────────────────────
16
17test('DeployConfig — set and get', async (t) => {
18 const bee = tmpBee()
19 await bee.ready()
20 const config = new DeployConfig(bee)
21
22 const result = await config.set('my-app', { enabled: true, mode: 'git', channel: 'release', autoDeploy: true })
23 t.is(result.enabled, true)
24 t.is(result.mode, 'git')
25 t.is(result.repoName, 'my-app')
26 t.ok(result.configuredAt)
27
28 const fetched = await config.get('my-app')
29 t.is(fetched.enabled, true)
30 t.is(fetched.channel, 'release')
31
32 await bee.close()
33})
34
35test('DeployConfig — set merges with existing', async (t) => {
36 const bee = tmpBee()
37 await bee.ready()
38 const config = new DeployConfig(bee)
39
40 await config.set('app', { enabled: true, mode: 'git' })
41 await config.set('app', { autoDeploy: true })
42
43 const fetched = await config.get('app')
44 t.is(fetched.enabled, true, 'preserved from first set')
45 t.is(fetched.autoDeploy, true, 'added in second set')
46
47 await bee.close()
48})
49
50test('DeployConfig — list all configs', async (t) => {
51 const bee = tmpBee()
52 await bee.ready()
53 const config = new DeployConfig(bee)
54
55 await config.set('app-a', { enabled: true })
56 await config.set('app-b', { enabled: false })
57
58 const all = await config.list()
59 t.is(all.length, 2)
60
61 await bee.close()
62})
63
64test('DeployConfig — isEnabled and shouldAutoDeploy', async (t) => {
65 const bee = tmpBee()
66 await bee.ready()
67 const config = new DeployConfig(bee)
68
69 t.is(await config.isEnabled('missing'), false)
70 t.is(await config.shouldAutoDeploy('missing'), false)
71
72 await config.set('app', { enabled: true, autoDeploy: false })
73 t.is(await config.isEnabled('app'), true)
74 t.is(await config.shouldAutoDeploy('app'), false)
75
76 await bee.close()
77})
78
79test('DeployConfig — delete', async (t) => {
80 const bee = tmpBee()
81 await bee.ready()
82 const config = new DeployConfig(bee)
83
84 await config.set('app', { enabled: true })
85 t.ok(await config.get('app'))
86 await config.delete('app')
87 t.is(await config.get('app'), null)
88
89 await bee.close()
90})
91
92// ── DeployLog ────────────────────────────────────────────────
93
94test('DeployLog — append and getLog', async (t) => {
95 const bee = tmpBee()
96 await bee.ready()
97 const log = new DeployLog(bee)
98
99 await log.append('app', { status: 'success', commitHash: 'abc123', duration: 5000 })
100 await log.append('app', { status: 'failed', error: 'timeout', duration: 120000 })
101
102 const entries = await log.getLog('app')
103 t.is(entries.length, 2)
104 t.is(entries[0].status, 'failed', 'newest first (reverse)')
105 t.is(entries[1].status, 'success')
106
107 await bee.close()
108})
109
110test('DeployLog — getLatest', async (t) => {
111 const bee = tmpBee()
112 await bee.ready()
113 const log = new DeployLog(bee)
114
115 t.is(await log.getLatest('app'), null, 'null when empty')
116
117 await log.append('app', { status: 'success', commitHash: 'first' })
118 await log.append('app', { status: 'success', commitHash: 'second' })
119
120 const latest = await log.getLatest('app')
121 t.is(latest.commitHash, 'second')
122
123 await bee.close()
124})
125
126test('DeployLog — clear', async (t) => {
127 const bee = tmpBee()
128 await bee.ready()
129 const log = new DeployLog(bee)
130
131 await log.append('app', { status: 'success' })
132 await log.append('app', { status: 'success' })
133 t.is((await log.getLog('app')).length, 2)
134
135 await log.clear('app')
136 t.is((await log.getLog('app')).length, 0)
137
138 await bee.close()
139})
140
141// ── getDeployStatus ──────────────────────────────────────────
142
143test('getDeployStatus — assembles from config + log', async (t) => {
144 const bee = tmpBee()
145 await bee.ready()
146 const config = new DeployConfig(bee)
147 const log = new DeployLog(bee)
148
149 await config.set('app', { enabled: true, mode: 'git', autoDeploy: true })
150 await log.append('app', { status: 'success', commitHash: 'abc', duration: 3000 })
151
152 const status = await getDeployStatus(config, log, 'app')
153 t.is(status.repoName, 'app')
154 t.is(status.configured, true)
155 t.is(status.enabled, true)
156 t.is(status.autoDeploy, true)
157 t.ok(status.lastDeploy)
158 t.is(status.lastDeploy.status, 'success')
159 t.is(status.lastDeploy.commitHash, 'abc')
160
161 await bee.close()
162})
163
164test('getDeployStatus — unconfigured repo', async (t) => {
165 const bee = tmpBee()
166 await bee.ready()
167 const config = new DeployConfig(bee)
168 const log = new DeployLog(bee)
169
170 const status = await getDeployStatus(config, log, 'unknown')
171 t.is(status.configured, false)
172 t.is(status.enabled, false)
173 t.is(status.lastDeploy, null)
174
175 await bee.close()
176})