@aiot-nexus/plugin-sdk 1.0.2 → 1.1.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.
@@ -0,0 +1,401 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.helloProofPayload = exports.NexusDevControlServer = void 0;
27
+ const crypto = __importStar(require("crypto"));
28
+ const fs = __importStar(require("fs"));
29
+ const net = __importStar(require("net"));
30
+ const os = __importStar(require("os"));
31
+ const path = __importStar(require("path"));
32
+ const dev_1 = require("./dev");
33
+ const protocolVersion = 1;
34
+ const maximumMessageBytes = 64 * 1024;
35
+ const requestTimeoutMs = 10000;
36
+ class NexusDevControlServer {
37
+ constructor() {
38
+ this.token = crypto.randomBytes(32).toString("hex");
39
+ this.pipePath = createPipePath();
40
+ this.receiveBuffer = Buffer.alloc(0);
41
+ this.helloAccepted = false;
42
+ this.authenticated = false;
43
+ this.pendingUpdates = new Map();
44
+ this.pendingShutdowns = new Map();
45
+ this.nextRequestId = 1;
46
+ }
47
+ async start() {
48
+ if (this.server) {
49
+ return;
50
+ }
51
+ await removeUnixSocket(this.pipePath);
52
+ const server = net.createServer((socket) => this.handleConnection(socket));
53
+ this.server = server;
54
+ await new Promise((resolve, reject) => {
55
+ const handleError = (error) => {
56
+ server.removeListener("listening", handleListening);
57
+ reject(error);
58
+ };
59
+ const handleListening = () => {
60
+ server.removeListener("error", handleError);
61
+ resolve();
62
+ };
63
+ server.once("error", handleError);
64
+ server.once("listening", handleListening);
65
+ server.listen(this.pipePath);
66
+ }).catch((error) => {
67
+ this.server = undefined;
68
+ throw new dev_1.PluginDevError("Plugin development control pipe could not start: " + errorMessage(error));
69
+ });
70
+ }
71
+ async waitForAuthentication(timeoutMs = 15000) {
72
+ if (this.authenticated) {
73
+ return;
74
+ }
75
+ const authenticationPromise = this.ensureAuthenticationPromise();
76
+ if (timeoutMs <= 0) {
77
+ await authenticationPromise;
78
+ return;
79
+ }
80
+ let timer;
81
+ try {
82
+ await Promise.race([
83
+ authenticationPromise,
84
+ new Promise((_resolve, reject) => {
85
+ timer = setTimeout(() => reject(new dev_1.PluginDevError("Nexus did not authenticate the plugin development control connection.")), timeoutMs);
86
+ })
87
+ ]);
88
+ }
89
+ finally {
90
+ if (timer) {
91
+ clearTimeout(timer);
92
+ }
93
+ }
94
+ }
95
+ sendUpdate(type, pluginId) {
96
+ if (!this.authenticated || !this.socket || this.socket.destroyed) {
97
+ return Promise.reject(new dev_1.PluginDevError("Nexus plugin development control connection is not ready."));
98
+ }
99
+ const requestId = String(process.pid) + "-" + String(this.nextRequestId++);
100
+ return new Promise((resolve, reject) => {
101
+ const timer = setTimeout(() => {
102
+ this.pendingUpdates.delete(requestId);
103
+ reject(new dev_1.PluginDevError("Nexus timed out while applying " + type + "."));
104
+ }, requestTimeoutMs);
105
+ this.pendingUpdates.set(requestId, { type, pluginId, resolve, reject, timer });
106
+ this.socket?.write(JSON.stringify({ type, pluginId, requestId }) + "\n");
107
+ });
108
+ }
109
+ requestShutdown() {
110
+ if (!this.authenticated || !this.socket || this.socket.destroyed) {
111
+ return Promise.reject(new dev_1.PluginDevError("Nexus plugin development control connection is not ready."));
112
+ }
113
+ const requestId = String(process.pid) + "-shutdown-" + String(this.nextRequestId++);
114
+ return new Promise((resolve, reject) => {
115
+ const timer = setTimeout(() => {
116
+ this.pendingShutdowns.delete(requestId);
117
+ reject(new dev_1.PluginDevError("Nexus timed out while accepting the plugin development restart."));
118
+ }, requestTimeoutMs);
119
+ this.pendingShutdowns.set(requestId, { resolve, reject, timer });
120
+ this.socket?.write(JSON.stringify({ type: "shutdown", requestId }) + "\n");
121
+ });
122
+ }
123
+ waitForDisconnection(timeoutMs = 10000) {
124
+ if (!this.socket) {
125
+ return Promise.resolve();
126
+ }
127
+ const disconnectionPromise = this.ensureDisconnectionPromise();
128
+ return withTimeout(disconnectionPromise, timeoutMs, "Nexus did not close the plugin development control connection.");
129
+ }
130
+ setDisconnectHandler(handler) {
131
+ this.disconnectHandler = handler;
132
+ return () => {
133
+ if (this.disconnectHandler === handler) {
134
+ this.disconnectHandler = undefined;
135
+ }
136
+ };
137
+ }
138
+ isConnected() {
139
+ return Boolean(this.authenticated && this.socket && !this.socket.destroyed);
140
+ }
141
+ async close() {
142
+ this.rejectPending(new dev_1.PluginDevError("Plugin development control connection closed."));
143
+ this.rejectPendingShutdowns(new dev_1.PluginDevError("Plugin development control connection closed."));
144
+ this.rejectAuthentication(new dev_1.PluginDevError("Plugin development control connection closed."));
145
+ if (this.socket) {
146
+ this.socket.removeAllListeners();
147
+ this.socket.destroy();
148
+ this.socket = undefined;
149
+ }
150
+ this.disconnectionResolve?.();
151
+ this.disconnectionPromise = undefined;
152
+ this.disconnectionResolve = undefined;
153
+ const server = this.server;
154
+ this.server = undefined;
155
+ if (server) {
156
+ await new Promise((resolve) => server.close(() => resolve()));
157
+ }
158
+ await removeUnixSocket(this.pipePath);
159
+ }
160
+ handleConnection(socket) {
161
+ if (this.socket) {
162
+ socket.destroy();
163
+ return;
164
+ }
165
+ this.socket = socket;
166
+ this.ensureAuthenticationPromise();
167
+ this.ensureDisconnectionPromise();
168
+ socket.on("data", (chunk) => this.handleData(socket, chunk));
169
+ socket.on("error", (error) => this.handleDisconnect(socket, error));
170
+ socket.on("close", () => this.handleDisconnect(socket, new dev_1.PluginDevError("Nexus plugin development control connection disconnected.")));
171
+ }
172
+ handleData(socket, chunk) {
173
+ if (socket !== this.socket) {
174
+ return;
175
+ }
176
+ this.receiveBuffer = Buffer.concat([this.receiveBuffer, chunk]);
177
+ while (true) {
178
+ const newlineIndex = this.receiveBuffer.indexOf(0x0a);
179
+ if (newlineIndex < 0) {
180
+ if (this.receiveBuffer.length > maximumMessageBytes) {
181
+ this.handleDisconnect(socket, new dev_1.PluginDevError("Nexus plugin development control message exceeded the size limit."));
182
+ }
183
+ return;
184
+ }
185
+ if (newlineIndex > maximumMessageBytes) {
186
+ this.handleDisconnect(socket, new dev_1.PluginDevError("Nexus plugin development control message exceeded the size limit."));
187
+ return;
188
+ }
189
+ const line = this.receiveBuffer.subarray(0, newlineIndex);
190
+ this.receiveBuffer = this.receiveBuffer.subarray(newlineIndex + 1);
191
+ if (line.length > 0 && !this.handleMessage(socket, line)) {
192
+ return;
193
+ }
194
+ }
195
+ }
196
+ handleMessage(socket, line) {
197
+ let value;
198
+ try {
199
+ value = JSON.parse(line.toString("utf8"));
200
+ }
201
+ catch {
202
+ this.handleDisconnect(socket, new dev_1.PluginDevError("Nexus plugin development control message is not valid JSON."));
203
+ return false;
204
+ }
205
+ if (!this.helloAccepted) {
206
+ if (!isHelloMessage(value) || !verifyHelloProof(value, this.token)) {
207
+ this.handleDisconnect(socket, new dev_1.PluginDevError("Nexus plugin development control proof is invalid."));
208
+ return false;
209
+ }
210
+ this.helloAccepted = true;
211
+ this.socket?.write(JSON.stringify({ type: "authenticate", token: this.token }) + "\n");
212
+ return true;
213
+ }
214
+ if (!this.authenticated && isAuthenticatedMessage(value)) {
215
+ this.authenticated = true;
216
+ this.authenticationResolve?.();
217
+ this.authenticationResolve = undefined;
218
+ this.authenticationReject = undefined;
219
+ return true;
220
+ }
221
+ if (!this.authenticated) {
222
+ this.handleDisconnect(socket, new dev_1.PluginDevError("Nexus plugin development control authentication response is invalid."));
223
+ return false;
224
+ }
225
+ if (isUpdateResult(value)) {
226
+ const pending = this.pendingUpdates.get(value.requestId);
227
+ if (!pending) {
228
+ return true;
229
+ }
230
+ if (value.updateType !== pending.type || value.pluginId !== pending.pluginId) {
231
+ const error = new dev_1.PluginDevError("Nexus plugin development update result did not match its request.");
232
+ this.pendingUpdates.delete(value.requestId);
233
+ clearTimeout(pending.timer);
234
+ pending.reject(error);
235
+ this.handleDisconnect(socket, error);
236
+ return false;
237
+ }
238
+ this.pendingUpdates.delete(value.requestId);
239
+ clearTimeout(pending.timer);
240
+ pending.resolve({
241
+ ok: value.ok,
242
+ message: value.message,
243
+ ...(value.requiresRestart ? { requiresRestart: true } : {})
244
+ });
245
+ return true;
246
+ }
247
+ if (isShutdownResult(value)) {
248
+ const pending = this.pendingShutdowns.get(value.requestId);
249
+ if (!pending) {
250
+ return true;
251
+ }
252
+ this.pendingShutdowns.delete(value.requestId);
253
+ clearTimeout(pending.timer);
254
+ if (value.ok) {
255
+ pending.resolve();
256
+ }
257
+ else {
258
+ pending.reject(new dev_1.PluginDevError(value.message));
259
+ }
260
+ return true;
261
+ }
262
+ this.handleDisconnect(socket, new dev_1.PluginDevError("Nexus plugin development control message is invalid."));
263
+ return false;
264
+ }
265
+ handleDisconnect(socket, error) {
266
+ if (socket !== this.socket) {
267
+ return;
268
+ }
269
+ const wasAuthenticated = this.authenticated;
270
+ this.socket = undefined;
271
+ this.helloAccepted = false;
272
+ this.authenticated = false;
273
+ this.receiveBuffer = Buffer.alloc(0);
274
+ if (socket && !socket.destroyed) {
275
+ socket.destroy();
276
+ }
277
+ this.rejectAuthentication(error);
278
+ this.rejectPending(error);
279
+ this.rejectPendingShutdowns(error);
280
+ this.disconnectionResolve?.();
281
+ this.disconnectionPromise = undefined;
282
+ this.disconnectionResolve = undefined;
283
+ if (wasAuthenticated) {
284
+ this.disconnectHandler?.(error);
285
+ }
286
+ }
287
+ rejectPending(error) {
288
+ for (const pending of this.pendingUpdates.values()) {
289
+ clearTimeout(pending.timer);
290
+ pending.reject(error);
291
+ }
292
+ this.pendingUpdates.clear();
293
+ }
294
+ rejectPendingShutdowns(error) {
295
+ for (const pending of this.pendingShutdowns.values()) {
296
+ clearTimeout(pending.timer);
297
+ pending.reject(error);
298
+ }
299
+ this.pendingShutdowns.clear();
300
+ }
301
+ ensureAuthenticationPromise() {
302
+ if (!this.authenticationPromise) {
303
+ this.authenticationPromise = new Promise((resolve, reject) => {
304
+ this.authenticationResolve = resolve;
305
+ this.authenticationReject = reject;
306
+ });
307
+ }
308
+ return this.authenticationPromise;
309
+ }
310
+ ensureDisconnectionPromise() {
311
+ if (!this.disconnectionPromise) {
312
+ this.disconnectionPromise = new Promise((resolve) => {
313
+ this.disconnectionResolve = resolve;
314
+ });
315
+ }
316
+ return this.disconnectionPromise;
317
+ }
318
+ rejectAuthentication(error) {
319
+ this.authenticationReject?.(error);
320
+ this.authenticationPromise = undefined;
321
+ this.authenticationReject = undefined;
322
+ this.authenticationResolve = undefined;
323
+ }
324
+ }
325
+ exports.NexusDevControlServer = NexusDevControlServer;
326
+ function withTimeout(promise, timeoutMs, message) {
327
+ return new Promise((resolve, reject) => {
328
+ const timer = setTimeout(() => reject(new dev_1.PluginDevError(message)), timeoutMs);
329
+ promise.then((value) => {
330
+ clearTimeout(timer);
331
+ resolve(value);
332
+ }, (error) => {
333
+ clearTimeout(timer);
334
+ reject(error);
335
+ });
336
+ });
337
+ }
338
+ function createPipePath() {
339
+ const suffix = String(process.pid) + "-" + crypto.randomBytes(12).toString("hex");
340
+ return process.platform === "win32"
341
+ ? "\\\\.\\pipe\\nexus-plugin-dev-" + suffix
342
+ : path.join(os.tmpdir(), "nexus-plugin-dev-" + suffix + ".sock");
343
+ }
344
+ function verifyHelloProof(value, token) {
345
+ if (value.proof === undefined) {
346
+ return true;
347
+ }
348
+ const expected = crypto.createHmac("sha256", token)
349
+ .update(helloProofPayload(value.protocolVersion, value.pid, value.nonce), "utf8")
350
+ .digest("hex");
351
+ return value.proof.length === expected.length
352
+ && crypto.timingSafeEqual(Buffer.from(value.proof, "utf8"), Buffer.from(expected, "utf8"));
353
+ }
354
+ function helloProofPayload(version, pid, nonce) {
355
+ return "nexus-plugin-dev:" + String(version) + ":" + String(pid) + ":" + nonce;
356
+ }
357
+ exports.helloProofPayload = helloProofPayload;
358
+ function isHelloMessage(value) {
359
+ if (!isRecord(value)) {
360
+ return false;
361
+ }
362
+ return value.type === "hello"
363
+ && value.protocolVersion === protocolVersion
364
+ && typeof value.pid === "number" && Number.isSafeInteger(value.pid) && value.pid > 0
365
+ && typeof value.nonce === "string" && /^[a-f0-9]{32}$/i.test(value.nonce)
366
+ && (value.proof === undefined || (typeof value.proof === "string" && /^[a-f0-9]{64}$/i.test(value.proof)));
367
+ }
368
+ function isAuthenticatedMessage(value) {
369
+ return isRecord(value)
370
+ && value.type === "authenticated"
371
+ && value.protocolVersion === protocolVersion
372
+ && typeof value.pid === "number" && Number.isSafeInteger(value.pid) && value.pid > 0;
373
+ }
374
+ function isUpdateResult(value) {
375
+ return isRecord(value)
376
+ && value.type === "updateResult"
377
+ && typeof value.requestId === "string"
378
+ && (value.updateType === "rendererUpdated" || value.updateType === "manifestUpdated")
379
+ && typeof value.pluginId === "string"
380
+ && typeof value.ok === "boolean"
381
+ && typeof value.message === "string"
382
+ && (value.requiresRestart === undefined || typeof value.requiresRestart === "boolean");
383
+ }
384
+ function isShutdownResult(value) {
385
+ return isRecord(value)
386
+ && value.type === "shutdownResult"
387
+ && typeof value.requestId === "string"
388
+ && typeof value.ok === "boolean"
389
+ && typeof value.message === "string";
390
+ }
391
+ function isRecord(value) {
392
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
393
+ }
394
+ async function removeUnixSocket(pipePath) {
395
+ if (process.platform !== "win32") {
396
+ await fs.promises.rm(pipePath, { force: true });
397
+ }
398
+ }
399
+ function errorMessage(error) {
400
+ return error instanceof Error ? error.message : String(error);
401
+ }
@@ -0,0 +1,9 @@
1
+ export interface PluginDeploymentTransaction {
2
+ readonly targetPath: string;
3
+ commit(): void;
4
+ rollback(): void;
5
+ }
6
+ export declare function resolveNexusExecutable(hostPath: string): string;
7
+ export declare function beginPluginPackageDeployment(packageDirectory: string, nexusExecutable: string, pluginId: string): PluginDeploymentTransaction;
8
+ export declare function beginRendererDeployment(rendererDirectory: string, deployedPluginDirectory: string): PluginDeploymentTransaction;
9
+ export declare function beginManifestAndRendererDeployment(manifestPath: string, rendererDirectory: string, deployedPluginDirectory: string): PluginDeploymentTransaction;
@@ -0,0 +1,195 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.beginManifestAndRendererDeployment = exports.beginRendererDeployment = exports.beginPluginPackageDeployment = exports.resolveNexusExecutable = void 0;
27
+ const crypto = __importStar(require("crypto"));
28
+ const fs = __importStar(require("fs"));
29
+ const path = __importStar(require("path"));
30
+ const dev_1 = require("./dev");
31
+ function resolveNexusExecutable(hostPath) {
32
+ const resolved = path.resolve(hostPath);
33
+ if (!fs.existsSync(resolved)) {
34
+ throw new dev_1.PluginDevError("Nexus host path does not exist: " + resolved);
35
+ }
36
+ const stat = fs.statSync(resolved);
37
+ const executable = stat.isDirectory() ? path.join(resolved, "nexus.exe") : resolved;
38
+ if (!fs.existsSync(executable) || !fs.statSync(executable).isFile()) {
39
+ throw new dev_1.PluginDevError("Nexus executable is missing: " + executable);
40
+ }
41
+ if (process.platform === "win32" && path.extname(executable).toLowerCase() !== ".exe") {
42
+ throw new dev_1.PluginDevError("Nexus host must be a Windows executable: " + executable);
43
+ }
44
+ return executable;
45
+ }
46
+ exports.resolveNexusExecutable = resolveNexusExecutable;
47
+ function beginPluginPackageDeployment(packageDirectory, nexusExecutable, pluginId) {
48
+ const pluginsDirectory = path.join(path.dirname(nexusExecutable), "Plugins");
49
+ fs.mkdirSync(pluginsDirectory, { recursive: true });
50
+ const targetDirectory = path.join(pluginsDirectory, pluginId);
51
+ assertDirectChild(pluginsDirectory, targetDirectory);
52
+ return activateReplacements([prepareReplacement(packageDirectory, targetDirectory)]);
53
+ }
54
+ exports.beginPluginPackageDeployment = beginPluginPackageDeployment;
55
+ function beginRendererDeployment(rendererDirectory, deployedPluginDirectory) {
56
+ const targetDirectory = path.join(deployedPluginDirectory, "renderer");
57
+ assertDirectChild(deployedPluginDirectory, targetDirectory);
58
+ return activateReplacements([prepareReplacement(rendererDirectory, targetDirectory)]);
59
+ }
60
+ exports.beginRendererDeployment = beginRendererDeployment;
61
+ function beginManifestAndRendererDeployment(manifestPath, rendererDirectory, deployedPluginDirectory) {
62
+ const targetManifestPath = path.join(deployedPluginDirectory, "manifest.json");
63
+ const targetRendererDirectory = path.join(deployedPluginDirectory, "renderer");
64
+ assertDirectChild(deployedPluginDirectory, targetManifestPath);
65
+ assertDirectChild(deployedPluginDirectory, targetRendererDirectory);
66
+ const prepared = prepareReplacements([
67
+ { sourcePath: rendererDirectory, targetPath: targetRendererDirectory },
68
+ { sourcePath: manifestPath, targetPath: targetManifestPath }
69
+ ]);
70
+ return activateReplacements(prepared);
71
+ }
72
+ exports.beginManifestAndRendererDeployment = beginManifestAndRendererDeployment;
73
+ function prepareReplacements(items) {
74
+ const prepared = [];
75
+ try {
76
+ for (const item of items) {
77
+ prepared.push(prepareReplacement(item.sourcePath, item.targetPath));
78
+ }
79
+ return prepared;
80
+ }
81
+ catch (error) {
82
+ rollbackPrepared(prepared);
83
+ throw error;
84
+ }
85
+ }
86
+ function prepareReplacement(sourcePath, targetPath) {
87
+ if (!fs.existsSync(sourcePath)) {
88
+ throw new dev_1.PluginDevError("Plugin deployment source is missing: " + sourcePath);
89
+ }
90
+ const parentDirectory = path.dirname(targetPath);
91
+ fs.mkdirSync(parentDirectory, { recursive: true });
92
+ const suffix = String(process.pid) + "-" + crypto.randomBytes(8).toString("hex");
93
+ const safeName = path.basename(targetPath).replace(/[^A-Za-z0-9._-]/g, "_");
94
+ const stagedPath = path.join(parentDirectory, ".nexus-plugin-dev-" + safeName + "-stage-" + suffix);
95
+ const backupPath = path.join(parentDirectory, ".nexus-plugin-dev-" + safeName + "-backup-" + suffix);
96
+ try {
97
+ copyPath(sourcePath, stagedPath);
98
+ }
99
+ catch (error) {
100
+ removePath(stagedPath);
101
+ throw new dev_1.PluginDevError("Could not stage plugin deployment: " + errorMessage(error));
102
+ }
103
+ return {
104
+ targetPath,
105
+ stagedPath,
106
+ backupPath,
107
+ targetExisted: fs.existsSync(targetPath),
108
+ activated: false
109
+ };
110
+ }
111
+ function activateReplacements(prepared) {
112
+ try {
113
+ for (const item of prepared) {
114
+ if (item.targetExisted) {
115
+ fs.renameSync(item.targetPath, item.backupPath);
116
+ }
117
+ try {
118
+ fs.renameSync(item.stagedPath, item.targetPath);
119
+ item.activated = true;
120
+ }
121
+ catch (error) {
122
+ if (item.targetExisted && fs.existsSync(item.backupPath)) {
123
+ fs.renameSync(item.backupPath, item.targetPath);
124
+ }
125
+ throw error;
126
+ }
127
+ }
128
+ }
129
+ catch (error) {
130
+ rollbackPrepared(prepared);
131
+ throw new dev_1.PluginDevError("Could not activate staged plugin deployment: " + errorMessage(error));
132
+ }
133
+ let finished = false;
134
+ return {
135
+ targetPath: prepared[0].targetPath,
136
+ commit() {
137
+ if (finished) {
138
+ return;
139
+ }
140
+ for (const item of prepared) {
141
+ removePath(item.backupPath);
142
+ removePath(item.stagedPath);
143
+ }
144
+ finished = true;
145
+ },
146
+ rollback() {
147
+ if (finished) {
148
+ return;
149
+ }
150
+ rollbackPrepared(prepared);
151
+ finished = true;
152
+ }
153
+ };
154
+ }
155
+ function rollbackPrepared(prepared) {
156
+ for (const item of prepared.slice().reverse()) {
157
+ if (item.activated) {
158
+ removePath(item.targetPath);
159
+ item.activated = false;
160
+ }
161
+ if (item.targetExisted && fs.existsSync(item.backupPath)) {
162
+ fs.renameSync(item.backupPath, item.targetPath);
163
+ }
164
+ removePath(item.stagedPath);
165
+ removePath(item.backupPath);
166
+ }
167
+ }
168
+ function copyPath(sourcePath, targetPath) {
169
+ const stat = fs.lstatSync(sourcePath);
170
+ if (stat.isSymbolicLink()) {
171
+ throw new Error("Symbolic links are not supported: " + sourcePath);
172
+ }
173
+ if (stat.isFile()) {
174
+ fs.copyFileSync(sourcePath, targetPath);
175
+ return;
176
+ }
177
+ if (!stat.isDirectory()) {
178
+ throw new Error("Unsupported deployment entry: " + sourcePath);
179
+ }
180
+ fs.mkdirSync(targetPath, { recursive: true });
181
+ for (const entry of fs.readdirSync(sourcePath, { withFileTypes: true })) {
182
+ copyPath(path.join(sourcePath, entry.name), path.join(targetPath, entry.name));
183
+ }
184
+ }
185
+ function removePath(value) {
186
+ fs.rmSync(value, { recursive: true, force: true });
187
+ }
188
+ function assertDirectChild(parentDirectory, targetPath) {
189
+ if (path.dirname(path.resolve(targetPath)).toLowerCase() !== path.resolve(parentDirectory).toLowerCase()) {
190
+ throw new dev_1.PluginDevError("Plugin deployment target escaped its expected directory: " + targetPath);
191
+ }
192
+ }
193
+ function errorMessage(error) {
194
+ return error instanceof Error ? error.message : String(error);
195
+ }
@@ -0,0 +1,21 @@
1
+ import { type PluginDebugSelection, type PluginDebugTarget, type PluginMainInspectorMode } from "./dev";
2
+ import { NexusDevControlServer } from "./nexus-dev-control";
3
+ import type { PluginManifest } from "./types";
4
+ export interface PluginNexusDevOptions extends PluginDebugSelection {
5
+ hostPath: string;
6
+ openDevTools: boolean;
7
+ inspectorMode?: PluginMainInspectorMode;
8
+ inspectorPort?: number;
9
+ }
10
+ export interface PluginNexusDevIo {
11
+ stdout(message: string): void;
12
+ stderr(message: string): void;
13
+ }
14
+ export type PluginSourceChangeKind = "renderer" | "manifest" | "restart";
15
+ export declare function runPluginNexusDev(options: PluginNexusDevOptions, io: PluginNexusDevIo): Promise<number>;
16
+ export declare function classifyPluginSourceChange(projectDirectory: string, changedPath: string): PluginSourceChangeKind | undefined;
17
+ export declare function manifestChangeRequiresRestart(previous: PluginManifest, next: PluginManifest): boolean;
18
+ export declare function mergePluginSourceChange(current: PluginSourceChangeKind | undefined, incoming: PluginSourceChangeKind): PluginSourceChangeKind;
19
+ export declare function createNexusHostArguments(control: Pick<NexusDevControlServer, "pipePath" | "token">, target: PluginDebugTarget, options: Pick<PluginNexusDevOptions, "openDevTools" | "inspectorMode" | "inspectorPort">): string[];
20
+ export declare function isRetryablePluginDeploymentError(error: unknown): boolean;
21
+ export declare function shouldSuppressNativeBuildEvent(changedPath: string, buildRunning: boolean, ignoreUntil: number, now?: number): boolean;