@holo-js/cli 0.1.2 → 0.1.4
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/dist/bin/holo.mjs +533 -4616
- package/dist/broadcast-CSSARTSA.mjs +84 -0
- package/dist/broadcast-YSIJCL3R.mjs +85 -0
- package/dist/cache-4G6QGIZO.mjs +66 -0
- package/dist/cache-OWQY4E7W.mjs +67 -0
- package/dist/cache-migrations-NATT5WPQ.mjs +154 -0
- package/dist/cache-migrations-RVEA6CEU.mjs +155 -0
- package/dist/chunk-3OTCSFDG.mjs +849 -0
- package/dist/chunk-66FHW725.mjs +465 -0
- package/dist/chunk-BWW5TDFI.mjs +4 -0
- package/dist/chunk-CUL4RJTG.mjs +22 -0
- package/dist/chunk-D4GG556Y.mjs +23 -0
- package/dist/chunk-D7O4SU6N.mjs +2 -0
- package/dist/chunk-DMH2B4UQ.mjs +343 -0
- package/dist/chunk-ET7UXHHQ.mjs +166 -0
- package/dist/chunk-EUIVXVJL.mjs +25 -0
- package/dist/chunk-G5ADO27Q.mjs +463 -0
- package/dist/chunk-GSQ3HTRO.mjs +165 -0
- package/dist/chunk-H7TJ4FB3.mjs +848 -0
- package/dist/chunk-ICJR7TS4.mjs +66 -0
- package/dist/chunk-JX2ZH6XY.mjs +270 -0
- package/dist/chunk-M7J3YTHR.mjs +26 -0
- package/dist/chunk-MZXN2YMI.mjs +3236 -0
- package/dist/chunk-Q5F6C2D4.mjs +65 -0
- package/dist/chunk-QFUSWV3J.mjs +3237 -0
- package/dist/chunk-QYLSMF7V.mjs +539 -0
- package/dist/chunk-S7P7EBM3.mjs +787 -0
- package/dist/chunk-SRWJU3A5.mjs +11 -0
- package/dist/chunk-URK7C3VQ.mjs +538 -0
- package/dist/chunk-VT5IDQG6.mjs +788 -0
- package/dist/chunk-XUYKPU5Q.mjs +272 -0
- package/dist/chunk-ZLRO7HXY.mjs +342 -0
- package/dist/chunk-ZXDU7RHU.mjs +9 -0
- package/dist/config-DMWBMMGD.mjs +26 -0
- package/dist/config-LS5USBRB.mjs +25 -0
- package/dist/dev-KQFT7RHR.mjs +43 -0
- package/dist/dev-LZ3O2E3U.mjs +42 -0
- package/dist/discovery-GBLAUTXS.mjs +28 -0
- package/dist/discovery-R733D2PO.mjs +29 -0
- package/dist/generators-DSN4GWJI.mjs +425 -0
- package/dist/generators-WX45BI4U.mjs +426 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.mjs +536 -4618
- package/dist/queue-6OG7VJ34.mjs +626 -0
- package/dist/queue-FV35LLPR.mjs +625 -0
- package/dist/queue-migrations-NK2EYX3J.mjs +163 -0
- package/dist/queue-migrations-SSIYKK5S.mjs +162 -0
- package/dist/runtime-4BV3JODY.mjs +56 -0
- package/dist/runtime-ANBO7VQM.mjs +33 -0
- package/dist/runtime-EFZ5H5IL.mjs +55 -0
- package/dist/runtime-OOSJ5JBY.mjs +32 -0
- package/dist/scaffold-7OTDH4UR.mjs +121 -0
- package/dist/scaffold-DRKBGS2K.mjs +120 -0
- package/dist/security-ATKDC26E.mjs +68 -0
- package/dist/security-R7VH6W5H.mjs +69 -0
- package/package.json +12 -11
|
@@ -0,0 +1,625 @@
|
|
|
1
|
+
import {
|
|
2
|
+
writeLine
|
|
3
|
+
} from "./chunk-ZXDU7RHU.mjs";
|
|
4
|
+
import {
|
|
5
|
+
initializeProjectRuntime
|
|
6
|
+
} from "./chunk-URK7C3VQ.mjs";
|
|
7
|
+
import "./chunk-EUIVXVJL.mjs";
|
|
8
|
+
import {
|
|
9
|
+
isIgnorableWatchError,
|
|
10
|
+
isRecursiveWatchUnsupported,
|
|
11
|
+
normalizeWatchedFilePath,
|
|
12
|
+
runProjectPrepare,
|
|
13
|
+
toPosixSlashes
|
|
14
|
+
} from "./chunk-ZLRO7HXY.mjs";
|
|
15
|
+
import "./chunk-CUL4RJTG.mjs";
|
|
16
|
+
import "./chunk-D7O4SU6N.mjs";
|
|
17
|
+
import {
|
|
18
|
+
prepareProjectDiscovery
|
|
19
|
+
} from "./chunk-S7P7EBM3.mjs";
|
|
20
|
+
import "./chunk-MZXN2YMI.mjs";
|
|
21
|
+
import {
|
|
22
|
+
ensureProjectConfig,
|
|
23
|
+
loadProjectConfig
|
|
24
|
+
} from "./chunk-GSQ3HTRO.mjs";
|
|
25
|
+
import {
|
|
26
|
+
loadGeneratedProjectRegistry
|
|
27
|
+
} from "./chunk-H7TJ4FB3.mjs";
|
|
28
|
+
import {
|
|
29
|
+
HOLO_RUNTIME_ROOT,
|
|
30
|
+
bundleProjectModule,
|
|
31
|
+
resolveProjectPackageImportSpecifier
|
|
32
|
+
} from "./chunk-G5ADO27Q.mjs";
|
|
33
|
+
|
|
34
|
+
// src/queue.ts
|
|
35
|
+
import { spawn } from "child_process";
|
|
36
|
+
import { existsSync, watch } from "fs";
|
|
37
|
+
import { mkdir, readdir, readFile, stat, writeFile } from "fs/promises";
|
|
38
|
+
import { dirname, extname, join, relative, resolve } from "path";
|
|
39
|
+
import { fileURLToPath, pathToFileURL } from "url";
|
|
40
|
+
import { loadConfigDirectory } from "@holo-js/config";
|
|
41
|
+
import {
|
|
42
|
+
configureDB,
|
|
43
|
+
resetDB,
|
|
44
|
+
resolveRuntimeConnectionManagerOptions
|
|
45
|
+
} from "@holo-js/db";
|
|
46
|
+
var QUEUE_LISTEN_SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
47
|
+
".cjs",
|
|
48
|
+
".cts",
|
|
49
|
+
".js",
|
|
50
|
+
".json",
|
|
51
|
+
".jsx",
|
|
52
|
+
".mjs",
|
|
53
|
+
".mts",
|
|
54
|
+
".ts",
|
|
55
|
+
".tsx"
|
|
56
|
+
]);
|
|
57
|
+
var QUEUE_LISTEN_IGNORED_DIRECTORY_NAMES = /* @__PURE__ */ new Set([
|
|
58
|
+
".git",
|
|
59
|
+
".next",
|
|
60
|
+
".nuxt",
|
|
61
|
+
".output",
|
|
62
|
+
".svelte-kit",
|
|
63
|
+
"coverage",
|
|
64
|
+
"dist",
|
|
65
|
+
"node_modules"
|
|
66
|
+
]);
|
|
67
|
+
var QUEUE_LISTEN_IGNORED_PATH_PREFIXES = [
|
|
68
|
+
HOLO_RUNTIME_ROOT
|
|
69
|
+
].map(toPosixSlashes);
|
|
70
|
+
async function loadQueueCliModule(projectRoot) {
|
|
71
|
+
return await import(resolveProjectPackageImportSpecifier(projectRoot, "@holo-js/queue"));
|
|
72
|
+
}
|
|
73
|
+
function isIgnoredQueueListenPath(filePath) {
|
|
74
|
+
const normalized = toPosixSlashes(filePath);
|
|
75
|
+
if (QUEUE_LISTEN_IGNORED_PATH_PREFIXES.some((prefix) => normalized === prefix || normalized.startsWith(`${prefix}/`))) {
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
return normalized.split("/").filter(Boolean).some((segment) => QUEUE_LISTEN_IGNORED_DIRECTORY_NAMES.has(segment));
|
|
79
|
+
}
|
|
80
|
+
async function collectQueueWatchTree(rootPath, directories, projectRoot, project) {
|
|
81
|
+
const relativePath = toPosixSlashes(relative(projectRoot, rootPath));
|
|
82
|
+
if (relativePath && isIgnoredQueueListenPath(relativePath)) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
const rootStats = await stat(rootPath).catch(() => void 0);
|
|
86
|
+
if (!rootStats?.isDirectory()) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
let shouldWatchDirectory = relativePath === "" || isQueueListenRelevantPath(relativePath, project);
|
|
90
|
+
const entries = await readdir(rootPath, { withFileTypes: true }).catch(() => []);
|
|
91
|
+
for (const entry of entries) {
|
|
92
|
+
const entryPath = join(rootPath, entry.name);
|
|
93
|
+
if (!entry.isDirectory()) {
|
|
94
|
+
if (entry.isFile()) {
|
|
95
|
+
const normalizedPath = toPosixSlashes(relative(projectRoot, entryPath));
|
|
96
|
+
if (isQueueListenRelevantPath(normalizedPath, project)) {
|
|
97
|
+
shouldWatchDirectory = true;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (await collectQueueWatchTree(entryPath, directories, projectRoot, project)) {
|
|
103
|
+
shouldWatchDirectory = true;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (shouldWatchDirectory) {
|
|
107
|
+
directories.add(rootPath);
|
|
108
|
+
}
|
|
109
|
+
return shouldWatchDirectory;
|
|
110
|
+
}
|
|
111
|
+
function isQueueListenRelevantPath(filePath, project) {
|
|
112
|
+
const normalized = toPosixSlashes(filePath);
|
|
113
|
+
const roots = [
|
|
114
|
+
project.config.paths.models,
|
|
115
|
+
project.config.paths.jobs,
|
|
116
|
+
project.config.paths.events,
|
|
117
|
+
project.config.paths.listeners,
|
|
118
|
+
"config",
|
|
119
|
+
".holo-js/generated"
|
|
120
|
+
];
|
|
121
|
+
if (normalized === ".env" || normalized.startsWith(".env.")) {
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
if (isIgnoredQueueListenPath(normalized)) {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
if (roots.some((root) => normalized === root || normalized.startsWith(`${toPosixSlashes(root)}/`))) {
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
return QUEUE_LISTEN_SOURCE_EXTENSIONS.has(extname(normalized).toLowerCase());
|
|
131
|
+
}
|
|
132
|
+
async function collectQueueWatchRoots(projectRoot, project) {
|
|
133
|
+
const directories = /* @__PURE__ */ new Set();
|
|
134
|
+
await collectQueueWatchTree(projectRoot, directories, projectRoot, project);
|
|
135
|
+
return [...directories];
|
|
136
|
+
}
|
|
137
|
+
function resolveCliEntrypointPath() {
|
|
138
|
+
const currentDir = dirname(fileURLToPath(import.meta.url));
|
|
139
|
+
const builtEntry = resolve(currentDir, "bin", "holo.mjs");
|
|
140
|
+
if (existsSync(builtEntry)) {
|
|
141
|
+
return builtEntry;
|
|
142
|
+
}
|
|
143
|
+
return resolve(currentDir, "bin", "holo.ts");
|
|
144
|
+
}
|
|
145
|
+
async function resolveRunnableCliEntrypoint() {
|
|
146
|
+
const cliEntrypoint = resolveCliEntrypointPath();
|
|
147
|
+
const extension = extname(cliEntrypoint).toLowerCase();
|
|
148
|
+
if (extension !== ".ts" && extension !== ".mts" && extension !== ".cts") {
|
|
149
|
+
return {
|
|
150
|
+
path: cliEntrypoint,
|
|
151
|
+
async cleanup() {
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
const currentDir = dirname(fileURLToPath(import.meta.url));
|
|
156
|
+
const workspaceRoot = resolve(currentDir, "..", "..", "..");
|
|
157
|
+
return bundleProjectModule(workspaceRoot, cliEntrypoint);
|
|
158
|
+
}
|
|
159
|
+
function isModuleRecord(value) {
|
|
160
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
161
|
+
}
|
|
162
|
+
function resolveModuleExport(moduleValue, matcher) {
|
|
163
|
+
if (!isModuleRecord(moduleValue)) return void 0;
|
|
164
|
+
if (matcher(moduleValue.default)) return moduleValue.default;
|
|
165
|
+
for (const value of Object.values(moduleValue)) {
|
|
166
|
+
if (matcher(value)) return value;
|
|
167
|
+
}
|
|
168
|
+
return void 0;
|
|
169
|
+
}
|
|
170
|
+
function buildQueueWorkArgs(flags) {
|
|
171
|
+
const args = ["queue:work"];
|
|
172
|
+
for (const [name, value] of Object.entries(flags)) {
|
|
173
|
+
if (name === "help" || name === "h") {
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if (value === false) {
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
const optionName = name.length === 1 ? `-${name}` : `--${name}`;
|
|
180
|
+
if (value === true) {
|
|
181
|
+
args.push(optionName);
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (Array.isArray(value)) {
|
|
185
|
+
for (const entry of value) {
|
|
186
|
+
args.push(optionName, entry);
|
|
187
|
+
}
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
args.push(optionName, String(value));
|
|
191
|
+
}
|
|
192
|
+
return args;
|
|
193
|
+
}
|
|
194
|
+
function resolveQueueRestartSignalPath(projectRoot) {
|
|
195
|
+
return resolve(projectRoot, ".holo-js", "runtime", "queue-restart.signal");
|
|
196
|
+
}
|
|
197
|
+
async function readQueueRestartSignal(projectRoot) {
|
|
198
|
+
const contents = await readFile(resolveQueueRestartSignalPath(projectRoot), "utf8").catch(() => void 0);
|
|
199
|
+
if (!contents) {
|
|
200
|
+
return void 0;
|
|
201
|
+
}
|
|
202
|
+
const parsed = Number.parseInt(contents.trim(), 10);
|
|
203
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
204
|
+
}
|
|
205
|
+
async function writeQueueRestartSignal(projectRoot, timestamp = Date.now()) {
|
|
206
|
+
const signalPath = resolveQueueRestartSignalPath(projectRoot);
|
|
207
|
+
await mkdir(dirname(signalPath), { recursive: true });
|
|
208
|
+
await writeFile(signalPath, `${timestamp}
|
|
209
|
+
`, "utf8");
|
|
210
|
+
return signalPath;
|
|
211
|
+
}
|
|
212
|
+
async function hasQueueRestartSignalSince(projectRoot, since) {
|
|
213
|
+
const signal = await readQueueRestartSignal(projectRoot);
|
|
214
|
+
return typeof signal === "number" && signal > since;
|
|
215
|
+
}
|
|
216
|
+
async function getQueueRuntimeEnvironment(projectRoot) {
|
|
217
|
+
let project = await loadProjectConfig(projectRoot, { required: true });
|
|
218
|
+
await prepareProjectDiscovery(projectRoot, project.config);
|
|
219
|
+
project = await loadProjectConfig(projectRoot, { required: true });
|
|
220
|
+
const runtime = await initializeProjectRuntime(projectRoot, {
|
|
221
|
+
registerProjectQueueJobs: false
|
|
222
|
+
});
|
|
223
|
+
const registry = await loadGeneratedProjectRegistry(projectRoot);
|
|
224
|
+
const modelEntries = registry?.models ?? [];
|
|
225
|
+
const jobEntries = registry?.jobs ?? [];
|
|
226
|
+
const bundledModels = [];
|
|
227
|
+
const bundledJobs = [];
|
|
228
|
+
try {
|
|
229
|
+
const queueModule = jobEntries.length > 0 ? await loadQueueCliModule(projectRoot) : void 0;
|
|
230
|
+
for (const entry of modelEntries) {
|
|
231
|
+
bundledModels.push(await bundleProjectModule(
|
|
232
|
+
projectRoot,
|
|
233
|
+
resolve(projectRoot, entry.sourcePath)
|
|
234
|
+
));
|
|
235
|
+
}
|
|
236
|
+
for (const entry of jobEntries) {
|
|
237
|
+
if (queueModule?.getRegisteredQueueJob(entry.name)) {
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
bundledJobs.push(await bundleProjectModule(
|
|
241
|
+
projectRoot,
|
|
242
|
+
resolve(projectRoot, entry.sourcePath),
|
|
243
|
+
{ external: ["@holo-js/queue"] }
|
|
244
|
+
));
|
|
245
|
+
}
|
|
246
|
+
for (let index = 0; index < modelEntries.length; index += 1) {
|
|
247
|
+
const bundledEntry = bundledModels[index];
|
|
248
|
+
await import(`${pathToFileURL(bundledEntry.path).href}?t=${Date.now()}-model-${index}`);
|
|
249
|
+
}
|
|
250
|
+
let bundledJobIndex = 0;
|
|
251
|
+
for (let index = 0; index < jobEntries.length; index += 1) {
|
|
252
|
+
const entry = jobEntries[index];
|
|
253
|
+
if (queueModule?.getRegisteredQueueJob(entry.name)) {
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
const bundledEntry = bundledJobs[bundledJobIndex];
|
|
257
|
+
bundledJobIndex += 1;
|
|
258
|
+
const moduleValue = await import(`${pathToFileURL(bundledEntry.path).href}?t=${Date.now()}-${index}`);
|
|
259
|
+
const job = resolveModuleExport(moduleValue, (value) => queueModule.isQueueJobDefinition(value));
|
|
260
|
+
if (!job) {
|
|
261
|
+
throw new Error(`Discovered job "${entry.sourcePath}" does not export a Holo job.`);
|
|
262
|
+
}
|
|
263
|
+
if (!queueModule?.getRegisteredQueueJob(entry.name)) {
|
|
264
|
+
queueModule.registerQueueJob(queueModule.normalizeQueueJobDefinition(job), {
|
|
265
|
+
name: entry.name,
|
|
266
|
+
sourcePath: entry.sourcePath
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
} catch (error) {
|
|
271
|
+
await runtime.shutdown().catch(() => {
|
|
272
|
+
});
|
|
273
|
+
await Promise.all([...bundledModels, ...bundledJobs].map((entry) => entry.cleanup()));
|
|
274
|
+
throw error;
|
|
275
|
+
}
|
|
276
|
+
return {
|
|
277
|
+
runtime,
|
|
278
|
+
project,
|
|
279
|
+
bundledJobs: bundledJobs.map((entry) => entry.path),
|
|
280
|
+
async cleanup() {
|
|
281
|
+
await Promise.all([...bundledModels, ...bundledJobs].map((entry) => entry.cleanup()));
|
|
282
|
+
await runtime.shutdown();
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
async function runQueueWorkCommand(io, projectRoot, options, dependencies = {}) {
|
|
287
|
+
const startedAt = Date.now();
|
|
288
|
+
const environment = await (dependencies.getEnvironment ?? getQueueRuntimeEnvironment)(projectRoot);
|
|
289
|
+
try {
|
|
290
|
+
const queueModule = dependencies.runWorker ? void 0 : await loadQueueCliModule(projectRoot);
|
|
291
|
+
const result = await (dependencies.runWorker ?? queueModule.runQueueWorker)({
|
|
292
|
+
...options,
|
|
293
|
+
shouldStop: async () => {
|
|
294
|
+
if (await options.shouldStop?.()) {
|
|
295
|
+
return true;
|
|
296
|
+
}
|
|
297
|
+
return (dependencies.hasRestartSignal ?? hasQueueRestartSignalSince)(projectRoot, startedAt);
|
|
298
|
+
},
|
|
299
|
+
onJobFailed: async (event) => {
|
|
300
|
+
await options.onJobFailed?.(event);
|
|
301
|
+
writeLine(io.stderr, `[queue] Failed ${event.jobName} (${event.jobId}): ${event.error.message}`);
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
writeLine(
|
|
305
|
+
io.stdout,
|
|
306
|
+
`[queue] Stopped (${result.stoppedBecause}). processed=${result.processed} released=${result.released} failed=${result.failed}`
|
|
307
|
+
);
|
|
308
|
+
} finally {
|
|
309
|
+
await environment.cleanup();
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
async function initializeQueueMaintenanceEnvironment(projectRoot, connectionName) {
|
|
313
|
+
const loadedConfig = await loadConfigDirectory(projectRoot);
|
|
314
|
+
const queueModule = await loadQueueCliModule(projectRoot);
|
|
315
|
+
const resolvedConnectionName = connectionName?.trim() || loadedConfig.queue.default;
|
|
316
|
+
const connection = loadedConfig.queue.connections[resolvedConnectionName];
|
|
317
|
+
if (!connection || connection.driver !== "database") {
|
|
318
|
+
queueModule.configureQueueRuntime({
|
|
319
|
+
config: loadedConfig.queue,
|
|
320
|
+
redisConfig: loadedConfig.redis
|
|
321
|
+
});
|
|
322
|
+
return {
|
|
323
|
+
async cleanup() {
|
|
324
|
+
await queueModule.shutdownQueueRuntime();
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
const { createQueueDbRuntimeOptions } = await import(resolveProjectPackageImportSpecifier(projectRoot, "@holo-js/queue-db"));
|
|
329
|
+
queueModule.configureQueueRuntime({
|
|
330
|
+
config: loadedConfig.queue,
|
|
331
|
+
redisConfig: loadedConfig.redis,
|
|
332
|
+
...createQueueDbRuntimeOptions()
|
|
333
|
+
});
|
|
334
|
+
const manager = resolveRuntimeConnectionManagerOptions({
|
|
335
|
+
db: loadedConfig.database
|
|
336
|
+
});
|
|
337
|
+
configureDB(manager);
|
|
338
|
+
try {
|
|
339
|
+
await manager.initializeAll();
|
|
340
|
+
} catch (error) {
|
|
341
|
+
await manager.disconnectAll().catch(() => {
|
|
342
|
+
});
|
|
343
|
+
resetDB();
|
|
344
|
+
await queueModule.shutdownQueueRuntime();
|
|
345
|
+
throw error;
|
|
346
|
+
}
|
|
347
|
+
return {
|
|
348
|
+
async cleanup() {
|
|
349
|
+
try {
|
|
350
|
+
await manager.disconnectAll();
|
|
351
|
+
} finally {
|
|
352
|
+
resetDB();
|
|
353
|
+
await queueModule.shutdownQueueRuntime();
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
async function runQueueClearCommand(io, projectRoot, connectionName, queueNames, dependencies = {}) {
|
|
359
|
+
if (dependencies.initialize) {
|
|
360
|
+
const runtime = await dependencies.initialize(projectRoot);
|
|
361
|
+
const queueModule = dependencies.clear ? void 0 : await loadQueueCliModule(projectRoot);
|
|
362
|
+
try {
|
|
363
|
+
const cleared = await (dependencies.clear ?? queueModule.clearQueueConnection)(connectionName, {
|
|
364
|
+
...queueNames && queueNames.length > 0 ? { queueNames } : {}
|
|
365
|
+
});
|
|
366
|
+
writeLine(io.stdout, `[queue] Cleared ${cleared} pending job(s).`);
|
|
367
|
+
} finally {
|
|
368
|
+
await runtime.shutdown();
|
|
369
|
+
}
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
const environment = await (dependencies.initializeQueue ?? initializeQueueMaintenanceEnvironment)(
|
|
373
|
+
projectRoot,
|
|
374
|
+
connectionName
|
|
375
|
+
);
|
|
376
|
+
try {
|
|
377
|
+
const queueModule = dependencies.clear ? void 0 : await loadQueueCliModule(projectRoot);
|
|
378
|
+
const cleared = await (dependencies.clear ?? queueModule.clearQueueConnection)(connectionName, {
|
|
379
|
+
...queueNames && queueNames.length > 0 ? { queueNames } : {}
|
|
380
|
+
});
|
|
381
|
+
writeLine(io.stdout, `[queue] Cleared ${cleared} pending job(s).`);
|
|
382
|
+
} finally {
|
|
383
|
+
await environment.cleanup();
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
async function runQueueRestartCommand(io, projectRoot) {
|
|
387
|
+
const signalPath = await writeQueueRestartSignal(projectRoot);
|
|
388
|
+
writeLine(io.stdout, `[queue] Restart signal written: ${signalPath}`);
|
|
389
|
+
}
|
|
390
|
+
async function runQueueListen(io, projectRoot, flags, spawnProcess = spawn, createWatcher = watch, prepare = runProjectPrepare) {
|
|
391
|
+
let project = await ensureProjectConfig(projectRoot);
|
|
392
|
+
let refreshNonRecursiveWatchers;
|
|
393
|
+
let requestChildRestart;
|
|
394
|
+
const childArgs = buildQueueWorkArgs(flags);
|
|
395
|
+
const cliEntrypoint = await resolveRunnableCliEntrypoint();
|
|
396
|
+
try {
|
|
397
|
+
const prepareDiscovery = async () => {
|
|
398
|
+
await prepare(projectRoot, io);
|
|
399
|
+
project = await ensureProjectConfig(projectRoot);
|
|
400
|
+
await refreshNonRecursiveWatchers?.();
|
|
401
|
+
};
|
|
402
|
+
await prepareDiscovery();
|
|
403
|
+
let pendingPrepare;
|
|
404
|
+
let queued = false;
|
|
405
|
+
let shuttingDown = false;
|
|
406
|
+
const rerunPrepare = () => {
|
|
407
|
+
if (shuttingDown) {
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
if (pendingPrepare) {
|
|
411
|
+
queued = true;
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
pendingPrepare = prepareDiscovery().then(() => {
|
|
415
|
+
requestChildRestart?.();
|
|
416
|
+
}).catch((error) => {
|
|
417
|
+
io.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
418
|
+
`);
|
|
419
|
+
}).finally(() => {
|
|
420
|
+
pendingPrepare = void 0;
|
|
421
|
+
if (queued) {
|
|
422
|
+
queued = false;
|
|
423
|
+
rerunPrepare();
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
};
|
|
427
|
+
const closeWatchers = (() => {
|
|
428
|
+
try {
|
|
429
|
+
const watcher = createWatcher(projectRoot, { recursive: true }, (_eventType, fileName) => {
|
|
430
|
+
if (shuttingDown || typeof fileName !== "string" || !isQueueListenRelevantPath(fileName, project)) {
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
rerunPrepare();
|
|
434
|
+
});
|
|
435
|
+
return () => watcher.close();
|
|
436
|
+
} catch (error) {
|
|
437
|
+
if (!isRecursiveWatchUnsupported(error)) {
|
|
438
|
+
throw error;
|
|
439
|
+
}
|
|
440
|
+
const watchers = [];
|
|
441
|
+
const closeAllWatchers = () => {
|
|
442
|
+
while (watchers.length > 0) {
|
|
443
|
+
watchers.pop()?.close();
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
refreshNonRecursiveWatchers = async () => {
|
|
447
|
+
closeAllWatchers();
|
|
448
|
+
const watchRoots = await collectQueueWatchRoots(projectRoot, project);
|
|
449
|
+
for (const watchRoot of watchRoots) {
|
|
450
|
+
try {
|
|
451
|
+
watchers.push(createWatcher(watchRoot, { recursive: false }, (_eventType, fileName) => {
|
|
452
|
+
if (shuttingDown || typeof fileName !== "string") {
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
const normalizedPath = normalizeWatchedFilePath(projectRoot, watchRoot, fileName);
|
|
456
|
+
if (!isQueueListenRelevantPath(normalizedPath, project)) {
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
rerunPrepare();
|
|
460
|
+
}));
|
|
461
|
+
} catch (watchError) {
|
|
462
|
+
if (!isIgnorableWatchError(watchError)) {
|
|
463
|
+
throw watchError;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
return () => closeAllWatchers();
|
|
469
|
+
}
|
|
470
|
+
})();
|
|
471
|
+
await refreshNonRecursiveWatchers?.();
|
|
472
|
+
while (!shuttingDown) {
|
|
473
|
+
const childStartedAt = Date.now();
|
|
474
|
+
const child = spawnProcess(process.execPath, [cliEntrypoint.path, ...childArgs], {
|
|
475
|
+
cwd: projectRoot,
|
|
476
|
+
env: process.env,
|
|
477
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
478
|
+
});
|
|
479
|
+
child.stdout?.on("data", (chunk) => io.stdout.write(chunk));
|
|
480
|
+
child.stderr?.on("data", (chunk) => io.stderr.write(chunk));
|
|
481
|
+
if (child.stdin) {
|
|
482
|
+
io.stdin.pipe(child.stdin);
|
|
483
|
+
}
|
|
484
|
+
const result = await new Promise((resolvePromise) => {
|
|
485
|
+
let restartRequested = false;
|
|
486
|
+
requestChildRestart = () => {
|
|
487
|
+
if (restartRequested || shuttingDown || typeof child.kill !== "function") {
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
restartRequested = true;
|
|
491
|
+
child.kill("SIGTERM");
|
|
492
|
+
};
|
|
493
|
+
child.on("error", (error) => {
|
|
494
|
+
if (child.stdin) {
|
|
495
|
+
io.stdin.unpipe(child.stdin);
|
|
496
|
+
}
|
|
497
|
+
requestChildRestart = void 0;
|
|
498
|
+
if (restartRequested) {
|
|
499
|
+
resolvePromise({ kind: "restart" });
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
resolvePromise({ kind: "error", error });
|
|
503
|
+
});
|
|
504
|
+
child.on("close", (code) => {
|
|
505
|
+
void (async () => {
|
|
506
|
+
if (child.stdin) {
|
|
507
|
+
io.stdin.unpipe(child.stdin);
|
|
508
|
+
}
|
|
509
|
+
requestChildRestart = void 0;
|
|
510
|
+
if (restartRequested) {
|
|
511
|
+
resolvePromise({ kind: "restart" });
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
if (!shuttingDown && code === 0 && await hasQueueRestartSignalSince(projectRoot, childStartedAt)) {
|
|
515
|
+
resolvePromise({ kind: "restart" });
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
resolvePromise({ kind: "close", code });
|
|
519
|
+
})();
|
|
520
|
+
});
|
|
521
|
+
});
|
|
522
|
+
if (result.kind === "restart") {
|
|
523
|
+
continue;
|
|
524
|
+
}
|
|
525
|
+
shuttingDown = true;
|
|
526
|
+
closeWatchers();
|
|
527
|
+
await Promise.resolve(pendingPrepare);
|
|
528
|
+
if (result.kind === "error") {
|
|
529
|
+
throw result.error;
|
|
530
|
+
}
|
|
531
|
+
if (result.code === 0) {
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
throw new Error(`Queue worker failed with exit code ${result.code ?? "unknown"}.`);
|
|
535
|
+
}
|
|
536
|
+
} finally {
|
|
537
|
+
await cliEntrypoint.cleanup();
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
async function runQueueFailedCommand(io, projectRoot, dependencies = {}) {
|
|
541
|
+
const runtime = await (dependencies.initialize ?? initializeProjectRuntime)(projectRoot, {
|
|
542
|
+
registerProjectQueueJobs: false
|
|
543
|
+
});
|
|
544
|
+
try {
|
|
545
|
+
const queueModule = dependencies.list ? void 0 : await loadQueueCliModule(projectRoot);
|
|
546
|
+
const failedJobs = await (dependencies.list ?? queueModule.listFailedQueueJobs)();
|
|
547
|
+
if (failedJobs.length === 0) {
|
|
548
|
+
writeLine(io.stdout, "[queue] No failed jobs.");
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
for (const job of failedJobs) {
|
|
552
|
+
writeLine(
|
|
553
|
+
io.stdout,
|
|
554
|
+
`${job.id} ${job.job.name} connection=${job.job.connection} queue=${job.job.queue} failedAt=${job.failedAt}`
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
} finally {
|
|
558
|
+
await runtime.shutdown();
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
async function runQueueRetryCommand(io, projectRoot, identifier, dependencies = {}) {
|
|
562
|
+
const runtime = await (dependencies.initialize ?? initializeProjectRuntime)(projectRoot, {
|
|
563
|
+
registerProjectQueueJobs: false
|
|
564
|
+
});
|
|
565
|
+
try {
|
|
566
|
+
const queueModule = dependencies.retry ? void 0 : await loadQueueCliModule(projectRoot);
|
|
567
|
+
const retried = await (dependencies.retry ?? queueModule.retryFailedQueueJobs)(identifier);
|
|
568
|
+
writeLine(io.stdout, `[queue] Retried ${retried} failed job(s).`);
|
|
569
|
+
} finally {
|
|
570
|
+
await runtime.shutdown();
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
async function runQueueForgetCommand(io, projectRoot, identifier, dependencies = {}) {
|
|
574
|
+
const runtime = await (dependencies.initialize ?? initializeProjectRuntime)(projectRoot, {
|
|
575
|
+
registerProjectQueueJobs: false
|
|
576
|
+
});
|
|
577
|
+
try {
|
|
578
|
+
const queueModule = dependencies.forget ? void 0 : await loadQueueCliModule(projectRoot);
|
|
579
|
+
const forgotten = await (dependencies.forget ?? queueModule.forgetFailedQueueJob)(identifier);
|
|
580
|
+
writeLine(io.stdout, forgotten ? `[queue] Forgot failed job ${identifier}.` : `[queue] Failed job ${identifier} was not found.`);
|
|
581
|
+
} finally {
|
|
582
|
+
await runtime.shutdown();
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
async function runQueueFlushCommand(io, projectRoot, dependencies = {}) {
|
|
586
|
+
const runtime = await (dependencies.initialize ?? initializeProjectRuntime)(projectRoot, {
|
|
587
|
+
registerProjectQueueJobs: false
|
|
588
|
+
});
|
|
589
|
+
try {
|
|
590
|
+
const queueModule = dependencies.flush ? void 0 : await loadQueueCliModule(projectRoot);
|
|
591
|
+
const flushed = await (dependencies.flush ?? queueModule.flushFailedQueueJobs)();
|
|
592
|
+
writeLine(io.stdout, `[queue] Flushed ${flushed} failed job(s).`);
|
|
593
|
+
} finally {
|
|
594
|
+
await runtime.shutdown();
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
export {
|
|
598
|
+
QUEUE_LISTEN_IGNORED_DIRECTORY_NAMES,
|
|
599
|
+
QUEUE_LISTEN_IGNORED_PATH_PREFIXES,
|
|
600
|
+
QUEUE_LISTEN_SOURCE_EXTENSIONS,
|
|
601
|
+
buildQueueWorkArgs,
|
|
602
|
+
collectQueueWatchRoots,
|
|
603
|
+
collectQueueWatchTree,
|
|
604
|
+
getQueueRuntimeEnvironment,
|
|
605
|
+
hasQueueRestartSignalSince,
|
|
606
|
+
initializeQueueMaintenanceEnvironment,
|
|
607
|
+
isIgnoredQueueListenPath,
|
|
608
|
+
isModuleRecord,
|
|
609
|
+
isQueueListenRelevantPath,
|
|
610
|
+
loadQueueCliModule,
|
|
611
|
+
readQueueRestartSignal,
|
|
612
|
+
resolveCliEntrypointPath,
|
|
613
|
+
resolveModuleExport,
|
|
614
|
+
resolveQueueRestartSignalPath,
|
|
615
|
+
resolveRunnableCliEntrypoint,
|
|
616
|
+
runQueueClearCommand,
|
|
617
|
+
runQueueFailedCommand,
|
|
618
|
+
runQueueFlushCommand,
|
|
619
|
+
runQueueForgetCommand,
|
|
620
|
+
runQueueListen,
|
|
621
|
+
runQueueRestartCommand,
|
|
622
|
+
runQueueRetryCommand,
|
|
623
|
+
runQueueWorkCommand,
|
|
624
|
+
writeQueueRestartSignal
|
|
625
|
+
};
|