@superblocksteam/sdk 2.0.59-next.0 → 2.0.59-next.10
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.
- package/.turbo/turbo-build.log +1 -1
- package/dist/cli-replacement/dev.d.mts +1 -0
- package/dist/cli-replacement/dev.d.mts.map +1 -1
- package/dist/cli-replacement/dev.mjs +104 -3
- package/dist/cli-replacement/dev.mjs.map +1 -1
- package/dist/dev-utils/dev-server.d.mts.map +1 -1
- package/dist/dev-utils/dev-server.mjs +16 -1
- package/dist/dev-utils/dev-server.mjs.map +1 -1
- package/dist/telemetry/logging.d.ts.map +1 -1
- package/dist/telemetry/logging.js +9 -4
- package/dist/telemetry/logging.js.map +1 -1
- package/dist/vite-plugin-generate-api-build-manifest.d.mts.map +1 -1
- package/dist/vite-plugin-generate-api-build-manifest.mjs +1 -2
- package/dist/vite-plugin-generate-api-build-manifest.mjs.map +1 -1
- package/package.json +14 -11
- package/src/cli-replacement/dev.mts +139 -3
- package/src/dev-utils/dev-server.mts +17 -1
- package/src/telemetry/logging.ts +8 -4
- package/src/vite-plugin-generate-api-build-manifest.mts +4 -2
- package/tsconfig.tsbuildinfo +1 -1
- package/turbo.json +1 -0
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import * as child_process from "node:child_process";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import * as readline from "node:readline";
|
|
3
4
|
import { promisify } from "node:util";
|
|
4
5
|
import { SpanStatusCode } from "@opentelemetry/api";
|
|
5
6
|
|
|
6
|
-
import { NotFoundError } from "@superblocksteam/shared";
|
|
7
|
+
import { ConflictError, NotFoundError } from "@superblocksteam/shared";
|
|
7
8
|
import { maskUnixSignals } from "@superblocksteam/util";
|
|
8
9
|
import { AiService } from "@superblocksteam/vite-plugin-file-sync/ai-service";
|
|
9
10
|
import {
|
|
@@ -40,12 +41,132 @@ import type { DraftInterface } from "@superblocksteam/vite-plugin-file-sync/draf
|
|
|
40
41
|
const exec = promisify(child_process.exec);
|
|
41
42
|
|
|
42
43
|
const passErrorToVSCode = (message: string | undefined, logger: Logger) => {
|
|
43
|
-
if (message) {
|
|
44
|
+
if (message && process.env.SUPERBLOCKS_VSCODE === "true") {
|
|
44
45
|
// Prefixing with `clierr:` will make the VS code extension capture this message and show it to the user.
|
|
45
46
|
logger.error(`clierr: ${JSON.stringify({ message })}`);
|
|
46
47
|
}
|
|
47
48
|
};
|
|
48
49
|
|
|
50
|
+
const promptUser = (question: string): Promise<string> => {
|
|
51
|
+
const rl = readline.createInterface({
|
|
52
|
+
input: process.stdin,
|
|
53
|
+
output: process.stdout,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
return new Promise((resolve) => {
|
|
57
|
+
rl.question(question, (answer) => {
|
|
58
|
+
rl.close();
|
|
59
|
+
resolve(answer.trim().toLowerCase());
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const getBooleanUserResponse = async (question: string): Promise<boolean> => {
|
|
65
|
+
const answer = await promptUser(question);
|
|
66
|
+
return answer === "y" || answer === "yes";
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
async function handleInactiveLockTakeover(
|
|
70
|
+
lockInfo: { ownerName: string; timeSinceLastActivityMs: number | null },
|
|
71
|
+
lockSvc: LockService,
|
|
72
|
+
span: any,
|
|
73
|
+
logger: Logger,
|
|
74
|
+
): Promise<void> {
|
|
75
|
+
const inactiveMinutes = Math.floor(
|
|
76
|
+
(lockInfo.timeSinceLastActivityMs ?? 0) / 1000 / 60,
|
|
77
|
+
);
|
|
78
|
+
const warningMessage = `⚠️ This branch is locked by ${lockInfo.ownerName}, but they've been inactive for more than ${inactiveMinutes} minutes.`;
|
|
79
|
+
const prompt = "Take over editing? (y/N)";
|
|
80
|
+
|
|
81
|
+
// If running in VSCode, send prompt to extension
|
|
82
|
+
if (process.env.SUPERBLOCKS_VSCODE === "true") {
|
|
83
|
+
logger.error(
|
|
84
|
+
`cliprompt: ${JSON.stringify({
|
|
85
|
+
type: "force-takeover",
|
|
86
|
+
message: `${warningMessage} ${prompt}`,
|
|
87
|
+
lockInfo: {
|
|
88
|
+
ownerName: lockInfo.ownerName,
|
|
89
|
+
inactiveMinutes,
|
|
90
|
+
},
|
|
91
|
+
})}`,
|
|
92
|
+
);
|
|
93
|
+
logger.warn("Waiting for user response in VSCode...");
|
|
94
|
+
span.setStatus({
|
|
95
|
+
code: SpanStatusCode.ERROR,
|
|
96
|
+
message: "Lock takeover prompt shown in VSCode",
|
|
97
|
+
});
|
|
98
|
+
throw new Error("Lock takeover prompt shown");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Prompt user to force takeover (CLI only)
|
|
102
|
+
logger.warn(warningMessage);
|
|
103
|
+
const shouldTakeover = await getBooleanUserResponse(prompt);
|
|
104
|
+
if (shouldTakeover) {
|
|
105
|
+
logger.warn("Taking over lock...");
|
|
106
|
+
await lockSvc.forceTakeover();
|
|
107
|
+
logger.info(
|
|
108
|
+
"Lock acquired successfully. Continuing with dev server startup...",
|
|
109
|
+
);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
logger.warn("Lock takeover declined. Exiting...");
|
|
114
|
+
span.setStatus({
|
|
115
|
+
code: SpanStatusCode.ERROR,
|
|
116
|
+
message: "Lock takeover declined",
|
|
117
|
+
});
|
|
118
|
+
throw new Error("Lock takeover declined");
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function handleActiveLock(
|
|
122
|
+
lockInfo: { ownerName: string; lockLifetimeMs: number },
|
|
123
|
+
error: Error,
|
|
124
|
+
span: any,
|
|
125
|
+
logger: Logger,
|
|
126
|
+
): never {
|
|
127
|
+
const activeMinutes = Math.floor(lockInfo.lockLifetimeMs / 1000 / 60);
|
|
128
|
+
const activeTime =
|
|
129
|
+
activeMinutes === 0 ? "less than a minute" : `${activeMinutes} minutes`;
|
|
130
|
+
const message = `🔒 This branch is currently locked by ${lockInfo.ownerName} (active for ${activeTime}).\nYou cannot run the dev server on this branch. Wait for them to finish editing and try again.`;
|
|
131
|
+
|
|
132
|
+
passErrorToVSCode(message, logger);
|
|
133
|
+
span.setStatus({
|
|
134
|
+
code: SpanStatusCode.ERROR,
|
|
135
|
+
message: JSON.stringify(error),
|
|
136
|
+
});
|
|
137
|
+
throw new Error(message);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function handleLockConflict(
|
|
141
|
+
error: ConflictError,
|
|
142
|
+
lockSvc: LockService,
|
|
143
|
+
span: any,
|
|
144
|
+
logger: Logger,
|
|
145
|
+
): Promise<void> {
|
|
146
|
+
let lockInfo;
|
|
147
|
+
try {
|
|
148
|
+
lockInfo = await lockSvc.inspectLock();
|
|
149
|
+
} catch (inspectError) {
|
|
150
|
+
logger.error("Failed to inspect lock", getErrorMeta(inspectError));
|
|
151
|
+
span.setStatus({
|
|
152
|
+
code: SpanStatusCode.ERROR,
|
|
153
|
+
message: JSON.stringify(error),
|
|
154
|
+
});
|
|
155
|
+
passErrorToVSCode(
|
|
156
|
+
(error as { context?: { message: string } }).context?.message,
|
|
157
|
+
logger,
|
|
158
|
+
);
|
|
159
|
+
throw new Error(`Failed to acquire lock on application: ${error}`);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Successfully inspected the lock
|
|
163
|
+
if (lockInfo.canForceTakeover) {
|
|
164
|
+
await handleInactiveLockTakeover(lockInfo, lockSvc, span, logger);
|
|
165
|
+
} else {
|
|
166
|
+
handleActiveLock(lockInfo, error, span, logger);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
49
170
|
async function readPkgJson(cwd: string) {
|
|
50
171
|
try {
|
|
51
172
|
const { readPackage } = await import("read-pkg");
|
|
@@ -116,6 +237,7 @@ export async function dev(options: {
|
|
|
116
237
|
/* user control of sync operations */
|
|
117
238
|
uploadFirst?: boolean;
|
|
118
239
|
downloadFirst?: boolean;
|
|
240
|
+
forceTakeover?: boolean;
|
|
119
241
|
/* only used for testing purposes */
|
|
120
242
|
skipSync?: boolean;
|
|
121
243
|
|
|
@@ -222,10 +344,24 @@ export async function dev(options: {
|
|
|
222
344
|
try {
|
|
223
345
|
await maskUnixSignals(async () => {
|
|
224
346
|
if (lockService) {
|
|
347
|
+
const lockSvc = lockService;
|
|
225
348
|
await tracer.startActiveSpan("acquiringInitialLock", async (span) => {
|
|
226
349
|
try {
|
|
227
|
-
|
|
350
|
+
// If --force-takeover flag is set, immediately force takeover instead of acquiring
|
|
351
|
+
if (options.forceTakeover) {
|
|
352
|
+
logger.info("Force takeover requested, taking over lock...");
|
|
353
|
+
await lockSvc.forceTakeover();
|
|
354
|
+
logger.info("Lock acquired successfully via force takeover.");
|
|
355
|
+
} else {
|
|
356
|
+
await lockSvc.acquireLock();
|
|
357
|
+
}
|
|
228
358
|
} catch (error) {
|
|
359
|
+
// If we got a conflict error (409), inspect the lock for more information
|
|
360
|
+
if (error instanceof ConflictError) {
|
|
361
|
+
await handleLockConflict(error, lockSvc, span, logger);
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
|
|
229
365
|
logger.error(
|
|
230
366
|
"Failed to acquire lock on application",
|
|
231
367
|
getErrorMeta(error),
|
|
@@ -184,7 +184,8 @@ export async function createDevServer({
|
|
|
184
184
|
req.url === "/_sb_connect" ||
|
|
185
185
|
req.url === "/_sb_disconnect" ||
|
|
186
186
|
req.url === "/_sb_health" ||
|
|
187
|
-
req.url === "/_sb_status"
|
|
187
|
+
req.url === "/_sb_status" ||
|
|
188
|
+
req.url === "/_sb_activity"
|
|
188
189
|
) {
|
|
189
190
|
return next();
|
|
190
191
|
}
|
|
@@ -431,6 +432,21 @@ export async function createDevServer({
|
|
|
431
432
|
}
|
|
432
433
|
});
|
|
433
434
|
|
|
435
|
+
app.get("/_sb_activity", async (_req, res) => {
|
|
436
|
+
res.setHeader("Content-Type", "application/json");
|
|
437
|
+
|
|
438
|
+
if (lockService) {
|
|
439
|
+
res.send({
|
|
440
|
+
isUserActive: lockService.wasRecentlyActive,
|
|
441
|
+
connectedUsers: lockService.connectedUsers,
|
|
442
|
+
timeSinceLastActivityMs: lockService.timeSinceLastActivity,
|
|
443
|
+
});
|
|
444
|
+
} else {
|
|
445
|
+
logger.error("Lock service not found, rejecting request");
|
|
446
|
+
viteReject(new Error("Lock service not found, rejecting request"));
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
|
|
434
450
|
process.on("SIGINT", async () => {
|
|
435
451
|
logger.info("SIGINT received");
|
|
436
452
|
await gracefulShutdown({ logger, serverInitiated: false });
|
package/src/telemetry/logging.ts
CHANGED
|
@@ -19,7 +19,6 @@ if (process.env.SUPERBLOCKS_IS_CSB === "true") {
|
|
|
19
19
|
activeTransports.push(
|
|
20
20
|
new transports.Console({
|
|
21
21
|
format: format.combine(
|
|
22
|
-
format.colorize(),
|
|
23
22
|
format.timestamp({
|
|
24
23
|
format: "HH:mm:ss.SSS",
|
|
25
24
|
}),
|
|
@@ -29,11 +28,12 @@ activeTransports.push(
|
|
|
29
28
|
process.env.SUPERBLOCKS_IS_CSB === "true"
|
|
30
29
|
? `${timestamp} (${process.pid}) ${message}`
|
|
31
30
|
: `${message}`;
|
|
32
|
-
if (level === "error") {
|
|
31
|
+
if (level === "error" && error !== undefined) {
|
|
33
32
|
return `${base} ${(error as ErrorMeta["error"]).message} ${(error as ErrorMeta["error"]).stack}`;
|
|
34
33
|
}
|
|
35
34
|
return base;
|
|
36
35
|
}),
|
|
36
|
+
format.colorize(),
|
|
37
37
|
),
|
|
38
38
|
}),
|
|
39
39
|
);
|
|
@@ -95,9 +95,13 @@ const logger: Logger = Object.freeze({
|
|
|
95
95
|
severityNumber: SeverityNumber.ERROR,
|
|
96
96
|
severityText: "ERROR",
|
|
97
97
|
body: message,
|
|
98
|
-
attributes: meta as unknown as AnyValueMap,
|
|
98
|
+
attributes: meta as unknown as AnyValueMap | undefined,
|
|
99
99
|
});
|
|
100
|
-
|
|
100
|
+
if (meta) {
|
|
101
|
+
winstonLogger.error(message, { error: meta.error });
|
|
102
|
+
} else {
|
|
103
|
+
winstonLogger.error(message);
|
|
104
|
+
}
|
|
101
105
|
},
|
|
102
106
|
});
|
|
103
107
|
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { resolveLanguageSpecificStepContentFromBlocks } from "@superblocksteam/util";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
getPageName,
|
|
5
|
+
getScopeIdFromName,
|
|
6
|
+
} from "@superblocksteam/vite-plugin-file-sync";
|
|
4
7
|
import { yellow, red } from "colorette";
|
|
5
8
|
import fg from "fast-glob";
|
|
6
9
|
import fs from "fs-extra";
|
|
7
10
|
import { createLogger } from "vite";
|
|
8
11
|
import yaml from "yaml";
|
|
9
|
-
import { getScopeIdFromName } from "../../../../vite-plugin-file-sync/dist/util.js";
|
|
10
12
|
import { getLogger } from "./telemetry/logging.js";
|
|
11
13
|
import type { DeleteMeLibraryApi } from "@superblocksteam/library-shared/types";
|
|
12
14
|
import type { Plugin } from "vite";
|