@highstate/backend 0.7.6 → 0.7.9
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/highstate.manifest.json +4 -4
- package/dist/index.js +260 -149
- package/dist/index.js.map +1 -1
- package/dist/library/package-resolution-worker.js +41 -0
- package/dist/library/package-resolution-worker.js.map +1 -0
- package/dist/library/worker/main.js +1 -4
- package/dist/library/worker/main.js.map +1 -1
- package/dist/shared/index.js +0 -1
- package/package.json +6 -6
- package/src/library/abstractions.ts +7 -7
- package/src/library/local.ts +294 -166
- package/src/library/package-resolution-worker.ts +70 -0
- package/src/library/worker/loader.ts +8 -7
- package/src/orchestrator/operation-workset.ts +6 -2
- package/src/project/manager.ts +40 -2
- package/src/runner/local.ts +14 -6
- package/dist/chunk-DGUM43GV.js +0 -11
- package/dist/chunk-DGUM43GV.js.map +0 -1
- package/dist/library/source-resolution-worker.js +0 -56
- package/dist/library/source-resolution-worker.js.map +0 -1
- package/src/library/source-resolution-worker.ts +0 -96
package/src/library/local.ts
CHANGED
@@ -5,19 +5,24 @@ import type {
|
|
5
5
|
ResolvedUnitSource,
|
6
6
|
} from "./abstractions"
|
7
7
|
import type { Logger } from "pino"
|
8
|
-
import type {
|
8
|
+
import type {
|
9
|
+
PackageResolutionResponse,
|
10
|
+
PackageResolutionWorkerData,
|
11
|
+
} from "./package-resolution-worker"
|
9
12
|
import type { WorkerRequest, WorkerResponse } from "./worker/protocol"
|
10
13
|
import { fileURLToPath } from "node:url"
|
11
14
|
import { EventEmitter, on } from "node:events"
|
12
15
|
import { Worker } from "node:worker_threads"
|
13
|
-
import {
|
16
|
+
import { resolve } from "node:path"
|
14
17
|
import { readFile } from "node:fs/promises"
|
15
|
-
import { type InstanceModel, isUnitModel
|
18
|
+
import { type InstanceModel, isUnitModel } from "@highstate/contract"
|
16
19
|
import Watcher from "watcher"
|
17
20
|
import { BetterLock } from "better-lock"
|
18
21
|
import { resolve as importMetaResolve } from "import-meta-resolve"
|
19
22
|
import { z } from "zod"
|
20
|
-
import { readPackageJSON
|
23
|
+
import { readPackageJSON } from "pkg-types"
|
24
|
+
import { runScript, installDependencies, addDependency } from "nypm"
|
25
|
+
import { flatMap, groupBy, map, pipe, unique } from "remeda"
|
21
26
|
import { resolveMainLocalProject, stringArrayType } from "../common"
|
22
27
|
import {
|
23
28
|
diffLibraries,
|
@@ -27,9 +32,8 @@ import {
|
|
27
32
|
} from "../shared"
|
28
33
|
|
29
34
|
export const localLibraryBackendConfig = z.object({
|
30
|
-
|
31
|
-
|
32
|
-
HIGHSTATE_BACKEND_LIBRARY_LOCAL_EXTRA_SOURCE_WATCH_PATHS: stringArrayType.default(""),
|
35
|
+
HIGHSTATE_BACKEND_LIBRARY_LOCAL_PACKAGES: stringArrayType.default("@highstate/library"),
|
36
|
+
HIGHSTATE_BACKEND_LIBRARY_LOCAL_WATCH_PATHS: stringArrayType.optional(),
|
33
37
|
})
|
34
38
|
|
35
39
|
interface Events {
|
@@ -37,9 +41,19 @@ interface Events {
|
|
37
41
|
resolvedUnitSource: [ResolvedUnitSource]
|
38
42
|
}
|
39
43
|
|
44
|
+
type HighstateManifestJson = {
|
45
|
+
sourceHashes?: Record<string, string>
|
46
|
+
}
|
47
|
+
|
48
|
+
type LibraryPackage = {
|
49
|
+
name: string
|
50
|
+
rootPath: string
|
51
|
+
dependencies: Set<string>
|
52
|
+
dependents: Set<string>
|
53
|
+
}
|
54
|
+
|
40
55
|
export class LocalLibraryBackend implements LibraryBackend {
|
41
|
-
private readonly
|
42
|
-
private readonly sourceWatcher: Watcher
|
56
|
+
private readonly watcher: Watcher
|
43
57
|
|
44
58
|
private readonly lock = new BetterLock()
|
45
59
|
private readonly eventEmitter = new EventEmitter<Events>()
|
@@ -47,37 +61,29 @@ export class LocalLibraryBackend implements LibraryBackend {
|
|
47
61
|
private library: LibraryModel | null = null
|
48
62
|
private worker: Worker | null = null
|
49
63
|
|
64
|
+
private readonly packages = new Map<string, LibraryPackage>()
|
50
65
|
private readonly resolvedUnitSources = new Map<string, ResolvedUnitSource>()
|
51
66
|
|
52
67
|
private constructor(
|
53
|
-
private readonly
|
54
|
-
|
55
|
-
extraSourceWatchPaths: string[],
|
68
|
+
private readonly libraryPackages: string[],
|
69
|
+
watchPaths: string[],
|
56
70
|
private readonly logger: Logger,
|
57
71
|
) {
|
58
|
-
this.
|
59
|
-
this.libraryWatcher.on("all", (event: string, path: string) => {
|
60
|
-
const prefixPath = modulePaths.find(modulePath => path.startsWith(modulePath))
|
61
|
-
|
62
|
-
this.logger.info({ msg: "library event", event, path: relative(prefixPath!, path) })
|
63
|
-
void this.lock.acquire(() => this.updateLibrary())
|
64
|
-
})
|
65
|
-
|
66
|
-
this.sourceWatcher = new Watcher([sourceBasePath, ...extraSourceWatchPaths], {
|
72
|
+
this.watcher = new Watcher(watchPaths, {
|
67
73
|
recursive: true,
|
68
74
|
ignoreInitial: true,
|
69
|
-
ignore: /\.git|node_modules/,
|
75
|
+
ignore: /\.git|node_modules|dist/,
|
70
76
|
})
|
71
77
|
|
72
|
-
this.
|
73
|
-
|
78
|
+
this.watcher.on("all", (event: string, path: string) => {
|
79
|
+
this.logger.debug({ event, path }, "library event")
|
80
|
+
|
81
|
+
if (!path.endsWith(".json") && !path.endsWith(".ts")) {
|
74
82
|
return
|
75
83
|
}
|
76
84
|
|
77
|
-
void this.
|
85
|
+
void this.handleFileEvent(path)
|
78
86
|
})
|
79
|
-
|
80
|
-
this.logger.debug({ msg: "initialized", modulePaths })
|
81
87
|
}
|
82
88
|
|
83
89
|
async loadLibrary(): Promise<LibraryModel> {
|
@@ -94,29 +100,34 @@ export class LocalLibraryBackend implements LibraryBackend {
|
|
94
100
|
}
|
95
101
|
}
|
96
102
|
|
97
|
-
|
98
|
-
return
|
99
|
-
// ensure library is loaded
|
100
|
-
const [library] = await this.getLibrary()
|
101
|
-
|
102
|
-
if (!this.resolvedUnitSources.size) {
|
103
|
-
await this.syncUnitSources(library)
|
104
|
-
}
|
105
|
-
|
103
|
+
getLoadedResolvedUnitSources(): Promise<ResolvedUnitSource[]> {
|
104
|
+
return this.lock.acquire(() => {
|
106
105
|
return Array.from(this.resolvedUnitSources.values())
|
107
106
|
})
|
108
107
|
}
|
109
108
|
|
110
|
-
async
|
109
|
+
async getResolvedUnitSources(unitTypes: string[]): Promise<ResolvedUnitSource[]> {
|
111
110
|
return await this.lock.acquire(async () => {
|
112
|
-
// ensure library is loaded
|
113
111
|
const [library] = await this.getLibrary()
|
114
112
|
|
115
|
-
|
116
|
-
|
113
|
+
const units = unitTypes.map(type => library.components[type]).filter(isUnitModel)
|
114
|
+
const packageNames = Object.keys(groupBy(units, unit => unit.source.package))
|
115
|
+
|
116
|
+
await this.ensureLibraryPackagesLoaded(packageNames, true)
|
117
|
+
|
118
|
+
const result: ResolvedUnitSource[] = []
|
119
|
+
|
120
|
+
for (const unitType of unitTypes) {
|
121
|
+
const resolvedUnitSource = this.resolvedUnitSources.get(unitType)
|
122
|
+
|
123
|
+
if (resolvedUnitSource) {
|
124
|
+
result.push(resolvedUnitSource)
|
125
|
+
} else {
|
126
|
+
this.logger.warn(`resolved unit source not found for unit: "%s"`, unitType)
|
127
|
+
}
|
117
128
|
}
|
118
129
|
|
119
|
-
return
|
130
|
+
return result
|
120
131
|
})
|
121
132
|
}
|
122
133
|
|
@@ -128,80 +139,6 @@ export class LocalLibraryBackend implements LibraryBackend {
|
|
128
139
|
}
|
129
140
|
}
|
130
141
|
|
131
|
-
async syncUnitSources(library: LibraryModel): Promise<void> {
|
132
|
-
const unitsToResolve = new Map<string, UnitModel>()
|
133
|
-
|
134
|
-
for (const component of Object.values(library.components)) {
|
135
|
-
if (!isUnitModel(component)) {
|
136
|
-
continue
|
137
|
-
}
|
138
|
-
|
139
|
-
const existingResolvedSource = this.resolvedUnitSources.get(component.type)
|
140
|
-
const expectedSource = JSON.stringify(component.source)
|
141
|
-
|
142
|
-
if (existingResolvedSource?.serializedSource !== expectedSource) {
|
143
|
-
unitsToResolve.set(component.type, component)
|
144
|
-
}
|
145
|
-
}
|
146
|
-
|
147
|
-
await this.runSourceResolution(unitsToResolve)
|
148
|
-
}
|
149
|
-
|
150
|
-
private async runSourceResolution(units: Map<string, UnitModel>): Promise<void> {
|
151
|
-
const workerPathUrl = importMetaResolve(
|
152
|
-
`@highstate/backend/source-resolution-worker`,
|
153
|
-
import.meta.url,
|
154
|
-
)
|
155
|
-
const workerPath = fileURLToPath(workerPathUrl)
|
156
|
-
|
157
|
-
const worker = new Worker(workerPath, {
|
158
|
-
workerData: {
|
159
|
-
requests: Array.from(units.values()).map(unit => ({
|
160
|
-
unitType: unit.type,
|
161
|
-
source: unit.source,
|
162
|
-
})),
|
163
|
-
sourceBasePath: this.sourceBasePath,
|
164
|
-
logLevel: "error",
|
165
|
-
},
|
166
|
-
})
|
167
|
-
|
168
|
-
for await (const [event] of on(worker, "message")) {
|
169
|
-
const eventData = event as {
|
170
|
-
type: string
|
171
|
-
results: readonly SourceResolutionResult[]
|
172
|
-
}
|
173
|
-
|
174
|
-
if (eventData.type !== "result") {
|
175
|
-
throw new Error(`Unexpected message type '${eventData.type}', expected 'result'`)
|
176
|
-
}
|
177
|
-
|
178
|
-
for (const result of eventData.results) {
|
179
|
-
const unit = units.get(result.unitType)
|
180
|
-
if (!unit) {
|
181
|
-
this.logger.warn("unit not found for resolved source", { unitType: result.unitType })
|
182
|
-
continue
|
183
|
-
}
|
184
|
-
|
185
|
-
const resolvedSource: ResolvedUnitSource = {
|
186
|
-
unitType: result.unitType,
|
187
|
-
serializedSource: JSON.stringify(unit.source),
|
188
|
-
projectPath: result.projectPath,
|
189
|
-
packageJsonPath: result.packageJsonPath,
|
190
|
-
allowedDependencies: result.allowedDependencies,
|
191
|
-
sourceHash: result.sourceHash,
|
192
|
-
}
|
193
|
-
|
194
|
-
this.resolvedUnitSources.set(result.unitType, resolvedSource)
|
195
|
-
this.eventEmitter.emit("resolvedUnitSource", resolvedSource)
|
196
|
-
}
|
197
|
-
|
198
|
-
this.logger.info("unit sources synced")
|
199
|
-
return
|
200
|
-
}
|
201
|
-
|
202
|
-
throw new Error("Worker ended without sending the result")
|
203
|
-
}
|
204
|
-
|
205
142
|
async evaluateCompositeInstances(
|
206
143
|
allInstances: InstanceModel[],
|
207
144
|
resolvedInputs: Record<string, Record<string, ResolvedInstanceInput[]>>,
|
@@ -270,12 +207,16 @@ export class LocalLibraryBackend implements LibraryBackend {
|
|
270
207
|
return [this.library, this.worker]
|
271
208
|
}
|
272
209
|
|
273
|
-
return await this.
|
210
|
+
return await this.reloadLibrary()
|
274
211
|
}
|
275
212
|
|
276
|
-
private async
|
277
|
-
this.logger.info("
|
278
|
-
|
213
|
+
private async reloadLibrary(): Promise<[LibraryModel, Worker]> {
|
214
|
+
this.logger.info("reloading library")
|
215
|
+
|
216
|
+
this.worker = this.createLibraryWorker({
|
217
|
+
modulePaths: this.libraryPackages,
|
218
|
+
logLevel: "silent",
|
219
|
+
})
|
279
220
|
|
280
221
|
for await (const [event] of on(this.worker, "message")) {
|
281
222
|
const eventData = event as {
|
@@ -302,45 +243,31 @@ export class LocalLibraryBackend implements LibraryBackend {
|
|
302
243
|
|
303
244
|
this.logger.info("library reloaded")
|
304
245
|
|
305
|
-
await this.syncUnitSources(eventData.library)
|
306
|
-
|
307
246
|
return [this.library, this.worker]
|
308
247
|
}
|
309
248
|
|
310
249
|
throw new Error("Worker ended without sending library model")
|
311
250
|
}
|
312
251
|
|
313
|
-
private
|
314
|
-
const
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
private async updateUnitSourceHashes(path: string): Promise<void> {
|
321
|
-
const packageJsonPath = await resolvePackageJSON(path)
|
322
|
-
const packageJson = await readPackageJSON(path)
|
323
|
-
const library = await this.loadLibrary()
|
324
|
-
|
325
|
-
const manifestPath = resolve(dirname(packageJsonPath), "dist", "highstate.manifest.json")
|
326
|
-
|
327
|
-
let manifest: HighstateManifestJson | undefined
|
328
|
-
try {
|
329
|
-
manifest = JSON.parse(await readFile(manifestPath, "utf8")) as HighstateManifestJson
|
330
|
-
} catch (error) {
|
331
|
-
this.logger.debug(
|
332
|
-
{ error },
|
333
|
-
`failed to read highstate manifest for package "%s"`,
|
334
|
-
packageJson.name,
|
252
|
+
private async reloadUnitManifest(libraryPackage: LibraryPackage): Promise<void> {
|
253
|
+
const library = this.library
|
254
|
+
if (!library) {
|
255
|
+
this.logger.warn(
|
256
|
+
`library not loaded, cannot reload unit manifest for package: "%s"`,
|
257
|
+
libraryPackage.name,
|
335
258
|
)
|
259
|
+
return
|
336
260
|
}
|
337
261
|
|
262
|
+
const manifest = await this.readLibraryPackageManifest(libraryPackage)
|
263
|
+
const packageJson = await readPackageJSON(libraryPackage.rootPath)
|
264
|
+
|
338
265
|
for (const unit of Object.values(library.components)) {
|
339
266
|
if (!isUnitModel(unit)) {
|
340
267
|
continue
|
341
268
|
}
|
342
269
|
|
343
|
-
if (unit.source.package !==
|
270
|
+
if (unit.source.package !== libraryPackage.name) {
|
344
271
|
continue
|
345
272
|
}
|
346
273
|
|
@@ -357,47 +284,248 @@ export class LocalLibraryBackend implements LibraryBackend {
|
|
357
284
|
}
|
358
285
|
|
359
286
|
const resolvedSource = this.resolvedUnitSources.get(unit.type)
|
360
|
-
if (!resolvedSource) {
|
361
|
-
this.logger.warn(`resolved source not found for unit: "%s"`, unit.type)
|
362
|
-
continue
|
363
|
-
}
|
364
287
|
|
365
288
|
const newResolvedSource: ResolvedUnitSource = {
|
366
|
-
|
289
|
+
unitType: unit.type,
|
367
290
|
sourceHash,
|
291
|
+
projectPath: resolve(libraryPackage.rootPath, relativePath),
|
292
|
+
allowedDependencies: Object.keys(packageJson.peerDependencies ?? {}),
|
293
|
+
}
|
294
|
+
|
295
|
+
if (
|
296
|
+
resolvedSource?.sourceHash === newResolvedSource.sourceHash &&
|
297
|
+
resolvedSource?.projectPath === newResolvedSource.projectPath
|
298
|
+
) {
|
299
|
+
continue
|
368
300
|
}
|
369
301
|
|
370
302
|
this.resolvedUnitSources.set(unit.type, newResolvedSource)
|
371
303
|
this.eventEmitter.emit("resolvedUnitSource", newResolvedSource)
|
372
|
-
this.logger.
|
304
|
+
this.logger.debug(`updated source for unit: "%s"`, unit.type)
|
373
305
|
}
|
374
306
|
}
|
375
307
|
|
376
|
-
|
377
|
-
|
308
|
+
private async ensureLibraryPackagesLoaded(
|
309
|
+
names: string[],
|
310
|
+
installIfNotFound = false,
|
311
|
+
): Promise<void> {
|
312
|
+
const packagesToLoad = names.filter(name => !this.packages.has(name))
|
313
|
+
|
314
|
+
if (packagesToLoad.length > 0) {
|
315
|
+
await this.loadLibraryPackages(packagesToLoad, installIfNotFound)
|
316
|
+
}
|
317
|
+
}
|
318
|
+
|
319
|
+
private async rebuildLibraryPackage(
|
320
|
+
libraryPackage: LibraryPackage,
|
321
|
+
installDeps = false,
|
322
|
+
updateDeps = false,
|
323
|
+
rebuiltPackages = new Set<string>(),
|
324
|
+
): Promise<void> {
|
325
|
+
if (rebuiltPackages.has(libraryPackage.name)) {
|
326
|
+
return
|
327
|
+
}
|
328
|
+
|
329
|
+
rebuiltPackages.add(libraryPackage.name)
|
330
|
+
|
331
|
+
if (installDeps) {
|
332
|
+
this.logger.info(`installing dependencies for package "${libraryPackage.name}"`)
|
333
|
+
await installDependencies({ cwd: libraryPackage.rootPath })
|
334
|
+
}
|
335
|
+
|
336
|
+
if (updateDeps) {
|
337
|
+
await this.updateLibraryPackageDependencies(libraryPackage)
|
338
|
+
}
|
339
|
+
|
340
|
+
this.logger.info(`rebuilding library package "${libraryPackage.name}" via build script`)
|
341
|
+
await runScript("build", { cwd: libraryPackage.rootPath })
|
342
|
+
|
343
|
+
if (this.libraryPackages.includes(libraryPackage.name)) {
|
344
|
+
await this.reloadLibrary()
|
345
|
+
} else {
|
346
|
+
await this.reloadUnitManifest(libraryPackage)
|
347
|
+
}
|
348
|
+
|
349
|
+
await this.rebuildLibraryPackageDependents(libraryPackage, rebuiltPackages)
|
350
|
+
}
|
351
|
+
|
352
|
+
private async updateLibraryPackageDependencies(libraryPackage: LibraryPackage): Promise<void> {
|
353
|
+
const packageJson = await readPackageJSON(libraryPackage.rootPath)
|
354
|
+
const parsedName = LocalLibraryBackend.parseDependencyName(libraryPackage.name)
|
355
|
+
|
356
|
+
const dependencyPackageNames = pipe(
|
357
|
+
[packageJson.dependencies, packageJson.devDependencies, packageJson.peerDependencies],
|
358
|
+
flatMap(deps => Object.keys(deps ?? {})),
|
359
|
+
unique(),
|
360
|
+
map(LocalLibraryBackend.parseDependencyName),
|
361
|
+
)
|
362
|
+
|
363
|
+
const sameScopeDependencies = dependencyPackageNames.filter(
|
364
|
+
dep => dep.scope === parsedName.scope && dep.name !== parsedName.name,
|
365
|
+
)
|
366
|
+
|
367
|
+
await this.ensureLibraryPackagesLoaded(sameScopeDependencies.map(dep => dep.name))
|
368
|
+
|
369
|
+
for (const dependency of sameScopeDependencies) {
|
370
|
+
const dependencyPackage = this.packages.get(dependency.name)
|
371
|
+
if (!dependencyPackage) {
|
372
|
+
this.logger.warn(`dependency package not found for graph update: "%s"`, dependency.name)
|
373
|
+
continue
|
374
|
+
}
|
375
|
+
|
376
|
+
libraryPackage.dependencies.add(dependency.name)
|
377
|
+
dependencyPackage.dependents.add(libraryPackage.name)
|
378
|
+
}
|
379
|
+
}
|
380
|
+
|
381
|
+
private async rebuildLibraryPackageDependents(
|
382
|
+
libraryPackage: LibraryPackage,
|
383
|
+
rebuiltPackages = new Set<string>(),
|
384
|
+
): Promise<void> {
|
385
|
+
const promises: Promise<void>[] = []
|
386
|
+
|
387
|
+
for (const dependent of libraryPackage.dependents) {
|
388
|
+
const dependentPackage = this.packages.get(dependent)
|
389
|
+
if (!dependentPackage) {
|
390
|
+
this.logger.warn(`dependent package not found for rebuild: "%s"`, dependent)
|
391
|
+
continue
|
392
|
+
}
|
393
|
+
|
394
|
+
promises.push(this.rebuildLibraryPackage(dependentPackage, false, false, rebuiltPackages))
|
395
|
+
}
|
396
|
+
|
397
|
+
await Promise.all(promises)
|
398
|
+
}
|
399
|
+
|
400
|
+
private static parseDependencyName(this: void, dependency: string) {
|
401
|
+
if (dependency.startsWith("@")) {
|
402
|
+
const parts = dependency.split("/")
|
403
|
+
|
404
|
+
return {
|
405
|
+
name: dependency,
|
406
|
+
scope: parts[0],
|
407
|
+
}
|
408
|
+
}
|
409
|
+
|
410
|
+
return {
|
411
|
+
name: dependency,
|
412
|
+
scope: null,
|
413
|
+
}
|
414
|
+
}
|
415
|
+
|
416
|
+
private async readLibraryPackageManifest(
|
417
|
+
libraryPackage: LibraryPackage,
|
418
|
+
): Promise<HighstateManifestJson | undefined> {
|
419
|
+
const manifestPath = resolve(libraryPackage.rootPath, "dist", "highstate.manifest.json")
|
420
|
+
|
421
|
+
try {
|
422
|
+
const manifest = JSON.parse(await readFile(manifestPath, "utf8")) as HighstateManifestJson
|
423
|
+
|
424
|
+
return manifest
|
425
|
+
} catch (error) {
|
426
|
+
this.logger.debug(
|
427
|
+
{ error },
|
428
|
+
`failed to read highstate manifest of package: "%s"`,
|
429
|
+
libraryPackage.name,
|
430
|
+
)
|
431
|
+
|
432
|
+
return undefined
|
433
|
+
}
|
434
|
+
}
|
435
|
+
|
436
|
+
private async loadLibraryPackages(names: string[], installIfNotFound = false): Promise<void> {
|
437
|
+
this.logger.info("loading library packages: %s", names.join(", "))
|
438
|
+
|
439
|
+
const missingPackages: string[] = []
|
440
|
+
const packagesToUpdate: LibraryPackage[] = []
|
441
|
+
|
442
|
+
const worker = this.createPackageResolutionWorker({ packageNames: names })
|
443
|
+
for await (const [event] of on(worker, "message")) {
|
444
|
+
const eventData = event as PackageResolutionResponse
|
445
|
+
|
446
|
+
if (eventData.type !== "result") {
|
447
|
+
continue
|
448
|
+
}
|
378
449
|
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
450
|
+
for (const result of eventData.results) {
|
451
|
+
if (result.type === "success") {
|
452
|
+
const libraryPackage: LibraryPackage = {
|
453
|
+
name: result.packageName,
|
454
|
+
rootPath: result.packageRootPath,
|
455
|
+
dependencies: new Set(),
|
456
|
+
dependents: new Set(),
|
457
|
+
}
|
458
|
+
|
459
|
+
this.packages.set(result.packageName, libraryPackage)
|
460
|
+
packagesToUpdate.push(libraryPackage)
|
461
|
+
|
462
|
+
this.logger.info(`loaded library package: "%s"`, result.packageName)
|
463
|
+
} else if (result.type === "not-found") {
|
464
|
+
missingPackages.push(result.packageName)
|
465
|
+
} else {
|
466
|
+
this.logger.error(
|
467
|
+
`failed to load library package "%s": %s`,
|
468
|
+
result.packageName,
|
469
|
+
result.error,
|
470
|
+
)
|
471
|
+
}
|
384
472
|
}
|
385
473
|
|
386
|
-
|
474
|
+
break
|
387
475
|
}
|
388
476
|
|
389
|
-
|
390
|
-
|
391
|
-
|
477
|
+
for (const libraryPackage of packagesToUpdate) {
|
478
|
+
await this.updateLibraryPackageDependencies(libraryPackage)
|
479
|
+
|
480
|
+
if (!this.libraryPackages.includes(libraryPackage.name)) {
|
481
|
+
await this.reloadUnitManifest(libraryPackage)
|
482
|
+
}
|
483
|
+
}
|
484
|
+
|
485
|
+
if (installIfNotFound && missingPackages.length > 0) {
|
486
|
+
this.logger.info("installing missing library packages: %s", missingPackages.join(", "))
|
487
|
+
await addDependency(missingPackages)
|
488
|
+
await this.loadLibraryPackages(missingPackages)
|
489
|
+
}
|
490
|
+
}
|
491
|
+
|
492
|
+
private async handleFileEvent(path: string): Promise<void> {
|
493
|
+
await this.lock.acquire(async () => {
|
494
|
+
const libraryPackage = this.packages.values().find(pkg => path.startsWith(pkg.rootPath))
|
495
|
+
|
496
|
+
if (libraryPackage) {
|
497
|
+
await this.rebuildLibraryPackage(libraryPackage)
|
498
|
+
}
|
499
|
+
})
|
500
|
+
}
|
501
|
+
|
502
|
+
private createLibraryWorker(workerData: Record<string, unknown>) {
|
503
|
+
const workerPathUrl = importMetaResolve(`@highstate/backend/library-worker`, import.meta.url)
|
504
|
+
const workerPath = fileURLToPath(workerPathUrl)
|
505
|
+
|
506
|
+
return new Worker(workerPath, { workerData })
|
507
|
+
}
|
508
|
+
|
509
|
+
private createPackageResolutionWorker(workerData: PackageResolutionWorkerData) {
|
510
|
+
const workerPathUrl = importMetaResolve(
|
511
|
+
`@highstate/backend/package-resolution-worker`,
|
512
|
+
import.meta.url,
|
513
|
+
)
|
514
|
+
const workerPath = fileURLToPath(workerPathUrl)
|
515
|
+
|
516
|
+
return new Worker(workerPath, { workerData })
|
517
|
+
}
|
518
|
+
|
519
|
+
static async create(config: z.infer<typeof localLibraryBackendConfig>, logger: Logger) {
|
520
|
+
let watchPaths = config.HIGHSTATE_BACKEND_LIBRARY_LOCAL_WATCH_PATHS
|
521
|
+
if (!watchPaths) {
|
392
522
|
const [projectPath] = await resolveMainLocalProject()
|
393
|
-
|
394
|
-
extraSourceWatchPaths.push(projectPath)
|
523
|
+
watchPaths = [resolve(projectPath, "packages")]
|
395
524
|
}
|
396
525
|
|
397
526
|
return new LocalLibraryBackend(
|
398
|
-
|
399
|
-
|
400
|
-
extraSourceWatchPaths,
|
527
|
+
config.HIGHSTATE_BACKEND_LIBRARY_LOCAL_PACKAGES,
|
528
|
+
watchPaths,
|
401
529
|
logger.child({ backend: "LibraryBackend", service: "LocalLibraryBackend" }),
|
402
530
|
)
|
403
531
|
}
|
@@ -0,0 +1,70 @@
|
|
1
|
+
import { parentPort, workerData } from "node:worker_threads"
|
2
|
+
import { dirname } from "node:path"
|
3
|
+
import { findPackageJSON } from "node:module"
|
4
|
+
import { realpath } from "node:fs/promises"
|
5
|
+
import pino, { type Level } from "pino"
|
6
|
+
|
7
|
+
export type PackageResolutionWorkerData = {
|
8
|
+
packageNames: string[]
|
9
|
+
logLevel?: Level
|
10
|
+
}
|
11
|
+
|
12
|
+
export type PackageResult = { packageName: string } & (
|
13
|
+
| {
|
14
|
+
type: "success"
|
15
|
+
packageRootPath: string
|
16
|
+
}
|
17
|
+
| {
|
18
|
+
type: "not-found"
|
19
|
+
}
|
20
|
+
| {
|
21
|
+
type: "error"
|
22
|
+
error: string
|
23
|
+
}
|
24
|
+
)
|
25
|
+
|
26
|
+
export type PackageResolutionResponse = {
|
27
|
+
type: "result"
|
28
|
+
results: PackageResult[]
|
29
|
+
}
|
30
|
+
|
31
|
+
const { packageNames, logLevel } = workerData as PackageResolutionWorkerData
|
32
|
+
|
33
|
+
const logger = pino({ name: "source-resolution-worker", level: logLevel ?? "silent" })
|
34
|
+
|
35
|
+
const results: PackageResult[] = []
|
36
|
+
|
37
|
+
for (const packageName of packageNames) {
|
38
|
+
try {
|
39
|
+
const path = findPackageJSON(packageName, import.meta.url)
|
40
|
+
if (!path) {
|
41
|
+
throw new Error(`Package "${packageName}" not found`)
|
42
|
+
}
|
43
|
+
|
44
|
+
results.push({
|
45
|
+
type: "success",
|
46
|
+
packageName,
|
47
|
+
packageRootPath: await realpath(dirname(path)),
|
48
|
+
})
|
49
|
+
} catch (error) {
|
50
|
+
logger.error({ error }, `failed to resolve package "%s"`, packageName)
|
51
|
+
|
52
|
+
if (error instanceof Error && error.message.includes("not found")) {
|
53
|
+
results.push({
|
54
|
+
type: "not-found",
|
55
|
+
packageName,
|
56
|
+
})
|
57
|
+
} else {
|
58
|
+
results.push({
|
59
|
+
type: "error",
|
60
|
+
packageName,
|
61
|
+
error: error instanceof Error ? error.message : String(error),
|
62
|
+
})
|
63
|
+
}
|
64
|
+
}
|
65
|
+
}
|
66
|
+
|
67
|
+
parentPort!.postMessage({
|
68
|
+
type: "result",
|
69
|
+
results,
|
70
|
+
})
|
@@ -1,10 +1,6 @@
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
2
|
-
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
3
|
-
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
4
|
-
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
5
|
-
|
6
1
|
import type { Logger } from "pino"
|
7
2
|
import type { Jiti } from "jiti"
|
3
|
+
import Module from "node:module"
|
8
4
|
import {
|
9
5
|
type Component,
|
10
6
|
type Entity,
|
@@ -16,8 +12,13 @@ import {
|
|
16
12
|
import { serializeFunction } from "@pulumi/pulumi/runtime/index.js"
|
17
13
|
import { sha256 } from "crypto-hash"
|
18
14
|
|
19
|
-
|
20
|
-
|
15
|
+
declare module "module" {
|
16
|
+
// eslint-disable-next-line @typescript-eslint/no-namespace
|
17
|
+
namespace Module {
|
18
|
+
function _load(request: unknown, parent: unknown, isMain: boolean): unknown
|
19
|
+
}
|
20
|
+
}
|
21
|
+
|
21
22
|
const originalLoad = Module._load
|
22
23
|
|
23
24
|
Module._load = function (request: unknown, parent: unknown, isMain: boolean) {
|