sliftutils 1.2.11 → 1.2.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.
@@ -1,4 +1,5 @@
1
1
  import fs from "fs";
2
+ import crypto from "crypto";
2
3
  import { delay } from "socket-function/src/batching";
3
4
  import { bundleEntryCaller } from "../bundler/bundleEntryCaller";
4
5
  import yargs from "yargs";
@@ -6,6 +7,8 @@ import { formatTime } from "socket-function/src/formatting/format";
6
7
  import path from "path";
7
8
  import { getAllFiles } from "../misc/fs";
8
9
 
10
+ const BUILD_HASH_PREFIX = "::SLIFT_HOT_RELOAD_BUILD_HASH::";
11
+
9
12
  async function main() {
10
13
  let time = Date.now();
11
14
  //todonext
@@ -138,6 +141,22 @@ async function main() {
138
141
  console.log(`Copied ${filesCopied} changed assets`);
139
142
  }
140
143
 
144
+ // Hash the bundled JS outputs so the watch server can broadcast a version
145
+ // identifier the extension can compare against on a poll.
146
+ let jsOutputs = (await fs.promises.readdir(yargObj.outputFolder))
147
+ .filter(f => f.endsWith(".js"))
148
+ .sort();
149
+ let hasher = crypto.createHash("sha256");
150
+ for (let file of jsOutputs) {
151
+ let buf = await fs.promises.readFile(path.join(yargObj.outputFolder, file));
152
+ hasher.update(file);
153
+ hasher.update("\0");
154
+ hasher.update(buf);
155
+ hasher.update("\0");
156
+ }
157
+ let buildHash = hasher.digest("hex").slice(0, 16);
158
+ console.log(`${BUILD_HASH_PREFIX} ${buildHash}`);
159
+
141
160
  let duration = Date.now() - time;
142
161
  console.log(`Extension build completed in ${formatTime(duration)}`);
143
162
  }
@@ -2,6 +2,8 @@ import { watchFilesAndTriggerHotReloading } from "socket-function/hot/HotReloadC
2
2
  import { isInBrowser, isInChromeExtension, isInChromeExtensionBackground, isInChromeExtensionContentScript } from "../misc/environment";
3
3
 
4
4
  const DEFAULT_WATCH_PORT = 9876;
5
+ const CONTENT_SCRIPT_POLL_INTERVAL_MS = 1000;
6
+ const HOT_RELOAD_HASH_MESSAGE = "sliftHotReload:getHash";
5
7
 
6
8
  export async function enableHotReloading(config?: {
7
9
  port?: number;
@@ -20,7 +22,7 @@ export async function enableHotReloading(config?: {
20
22
  }
21
23
  }
22
24
 
23
- function watchPortHotReload(port = DEFAULT_WATCH_PORT, onReload: () => void) {
25
+ function watchPortHotReload(port = DEFAULT_WATCH_PORT, onReload: () => void, onHash?: (hash: string | undefined) => void) {
24
26
  let reconnectTimer: number | undefined;
25
27
  let ws: WebSocket | undefined;
26
28
 
@@ -39,8 +41,12 @@ function watchPortHotReload(port = DEFAULT_WATCH_PORT, onReload: () => void) {
39
41
  try {
40
42
  let data = JSON.parse(event.data);
41
43
  if (data.type === "build-complete" && data.success) {
42
- console.log("[Hot Reload] Build complete, reloading page...");
43
- onReload();
44
+ if (onHash) {
45
+ onHash(data.hash);
46
+ } else {
47
+ console.log("[Hot Reload] Build complete, reloading page...");
48
+ onReload();
49
+ }
44
50
  }
45
51
  } catch (error) {
46
52
  console.error("[Hot Reload] Failed to parse message:", error);
@@ -71,22 +77,55 @@ function watchPortHotReload(port = DEFAULT_WATCH_PORT, onReload: () => void) {
71
77
  }
72
78
 
73
79
  function chromeExtensionBackgroundHotReload(port = DEFAULT_WATCH_PORT) {
74
- chrome.runtime.onConnect.addListener((port) => {
75
- if (port.name === "hotReload") {
76
- // Keep the port open so content scripts can detect when we disconnect
80
+ let currentHash: string | undefined;
81
+
82
+ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
83
+ if (message && message.type === HOT_RELOAD_HASH_MESSAGE) {
84
+ sendResponse({ hash: currentHash });
85
+ return true;
77
86
  }
87
+ return undefined;
78
88
  });
79
89
 
80
- watchPortHotReload(port, () => {
81
- chrome.runtime.reload();
90
+ watchPortHotReload(port, () => { /* unused — hash callback drives reloads */ }, (hash) => {
91
+ if (!hash) return;
92
+ if (currentHash === undefined) {
93
+ currentHash = hash;
94
+ console.log(`[Hot Reload] Initial build hash: ${hash}`);
95
+ return;
96
+ }
97
+ if (hash !== currentHash) {
98
+ console.log(`[Hot Reload] Build hash changed (${currentHash} -> ${hash}), reloading extension...`);
99
+ currentHash = hash;
100
+ chrome.runtime.reload();
101
+ }
82
102
  });
83
103
  }
84
104
 
85
105
  function chromeExtensionContentScriptHotReload() {
86
- let port = chrome.runtime.connect({ name: "hotReload" });
106
+ let lastHash: string | undefined;
87
107
 
88
- port.onDisconnect.addListener(() => {
89
- console.log("[Hot Reload] Extension reloaded, refreshing page...");
90
- window.location.reload();
91
- });
108
+ setInterval(() => {
109
+ try {
110
+ chrome.runtime.sendMessage({ type: HOT_RELOAD_HASH_MESSAGE }, (response) => {
111
+ if (chrome.runtime.lastError) {
112
+ console.warn("[Hot Reload] sendMessage error:", chrome.runtime.lastError.message);
113
+ return;
114
+ }
115
+ let hash: string | undefined = response?.hash;
116
+ if (!hash) return;
117
+ if (lastHash === undefined) {
118
+ lastHash = hash;
119
+ return;
120
+ }
121
+ if (hash !== lastHash) {
122
+ console.log(`[Hot Reload] Build hash changed (${lastHash} -> ${hash}), refreshing page...`);
123
+ lastHash = hash;
124
+ window.location.reload();
125
+ }
126
+ });
127
+ } catch (error) {
128
+ // Extension context can be transiently invalidated during reload.
129
+ }
130
+ }, CONTENT_SCRIPT_POLL_INTERVAL_MS);
92
131
  }
package/builders/watch.ts CHANGED
@@ -5,6 +5,7 @@ import { WebSocketServer } from "ws";
5
5
 
6
6
  const DEBOUNCE_DELAY_MS = 250;
7
7
  const DEFAULT_WATCH_PORT = 9876;
8
+ const BUILD_HASH_REGEX = /::SLIFT_HOT_RELOAD_BUILD_HASH::\s+(\S+)/g;
8
9
 
9
10
  async function main() {
10
11
  let args = process.argv.slice(2);
@@ -48,6 +49,9 @@ async function main() {
48
49
 
49
50
  wss.on("connection", (ws) => {
50
51
  console.log(`[${new Date().toLocaleTimeString()}] WebSocket client connected`);
52
+ if (latestBuildHash) {
53
+ ws.send(JSON.stringify({ type: "build-complete", success: true, hash: latestBuildHash }));
54
+ }
51
55
  ws.on("close", () => {
52
56
  console.log(`[${new Date().toLocaleTimeString()}] WebSocket client disconnected`);
53
57
  });
@@ -63,6 +67,7 @@ async function main() {
63
67
  let debounceTimer: NodeJS.Timeout | undefined;
64
68
  let isRunning = false;
65
69
  let needsRerun = false;
70
+ let latestBuildHash: string | undefined;
66
71
 
67
72
  async function executeCommand() {
68
73
  if (isRunning) {
@@ -75,14 +80,25 @@ async function main() {
75
80
 
76
81
  try {
77
82
  console.log(`\n[${new Date().toLocaleTimeString()}] Running: ${command}`);
78
- await runPromise(command);
83
+ let output = await runPromise(command);
79
84
  console.log(`[${new Date().toLocaleTimeString()}] Completed successfully`);
80
85
 
86
+ // Extract the most recent [BUILD_HASH] line emitted by the build.
87
+ let match: RegExpExecArray | null;
88
+ let lastHash: string | undefined;
89
+ BUILD_HASH_REGEX.lastIndex = 0;
90
+ while ((match = BUILD_HASH_REGEX.exec(output)) !== null) {
91
+ lastHash = match[1];
92
+ }
93
+ if (lastHash) {
94
+ latestBuildHash = lastHash;
95
+ }
96
+
81
97
  // Notify all connected WebSocket clients
82
98
  if (wss) {
83
99
  wss.clients.forEach((client) => {
84
100
  if (client.readyState === 1) { // 1 = OPEN
85
- client.send(JSON.stringify({ type: "build-complete", success: true }));
101
+ client.send(JSON.stringify({ type: "build-complete", success: true, hash: latestBuildHash }));
86
102
  }
87
103
  });
88
104
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sliftutils",
3
- "version": "1.2.11",
3
+ "version": "1.2.13",
4
4
  "main": "index.js",
5
5
  "license": "MIT",
6
6
  "files": [