@highstate/backend 0.7.8 → 0.7.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/dist/highstate.manifest.json +4 -4
- package/dist/index.js +267 -155
- 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 -5
- package/src/library/abstractions.ts +7 -7
- package/src/library/local.ts +296 -171
- 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/local.ts +5 -4
- 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,54 +243,37 @@ 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
|
|
347
274
|
// TODO: resolve the path
|
348
|
-
const relativePath = unit.source.path
|
349
|
-
|
350
|
-
: `./dist/index.js`
|
351
|
-
|
352
|
-
const sourceHash = manifest?.sourceHashes?.[relativePath]
|
275
|
+
const relativePath = unit.source.path ? `./dist/${unit.source.path}` : `./dist`
|
276
|
+
const sourceHash = manifest?.sourceHashes?.[`${relativePath}/index.js`]
|
353
277
|
|
354
278
|
if (!sourceHash) {
|
355
279
|
this.logger.warn(`source hash not found for unit: "%s"`, unit.type)
|
@@ -357,47 +281,248 @@ export class LocalLibraryBackend implements LibraryBackend {
|
|
357
281
|
}
|
358
282
|
|
359
283
|
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
284
|
|
365
285
|
const newResolvedSource: ResolvedUnitSource = {
|
366
|
-
|
286
|
+
unitType: unit.type,
|
367
287
|
sourceHash,
|
288
|
+
projectPath: resolve(libraryPackage.rootPath, relativePath),
|
289
|
+
allowedDependencies: Object.keys(packageJson.peerDependencies ?? {}),
|
290
|
+
}
|
291
|
+
|
292
|
+
if (
|
293
|
+
resolvedSource?.sourceHash === newResolvedSource.sourceHash &&
|
294
|
+
resolvedSource?.projectPath === newResolvedSource.projectPath
|
295
|
+
) {
|
296
|
+
continue
|
368
297
|
}
|
369
298
|
|
370
299
|
this.resolvedUnitSources.set(unit.type, newResolvedSource)
|
371
300
|
this.eventEmitter.emit("resolvedUnitSource", newResolvedSource)
|
372
|
-
this.logger.
|
301
|
+
this.logger.debug(`updated source for unit: "%s"`, unit.type)
|
373
302
|
}
|
374
303
|
}
|
375
304
|
|
376
|
-
|
377
|
-
|
305
|
+
private async ensureLibraryPackagesLoaded(
|
306
|
+
names: string[],
|
307
|
+
installIfNotFound = false,
|
308
|
+
): Promise<void> {
|
309
|
+
const packagesToLoad = names.filter(name => !this.packages.has(name))
|
378
310
|
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
311
|
+
if (packagesToLoad.length > 0) {
|
312
|
+
await this.loadLibraryPackages(packagesToLoad, installIfNotFound)
|
313
|
+
}
|
314
|
+
}
|
315
|
+
|
316
|
+
private async rebuildLibraryPackage(
|
317
|
+
libraryPackage: LibraryPackage,
|
318
|
+
installDeps = false,
|
319
|
+
updateDeps = false,
|
320
|
+
rebuiltPackages = new Set<string>(),
|
321
|
+
): Promise<void> {
|
322
|
+
if (rebuiltPackages.has(libraryPackage.name)) {
|
323
|
+
return
|
324
|
+
}
|
325
|
+
|
326
|
+
rebuiltPackages.add(libraryPackage.name)
|
327
|
+
|
328
|
+
if (installDeps) {
|
329
|
+
this.logger.info(`installing dependencies for package "${libraryPackage.name}"`)
|
330
|
+
await installDependencies({ cwd: libraryPackage.rootPath })
|
331
|
+
}
|
332
|
+
|
333
|
+
if (updateDeps) {
|
334
|
+
await this.updateLibraryPackageDependencies(libraryPackage)
|
335
|
+
}
|
336
|
+
|
337
|
+
this.logger.info(`rebuilding library package "${libraryPackage.name}" via build script`)
|
338
|
+
await runScript("build", { cwd: libraryPackage.rootPath })
|
339
|
+
|
340
|
+
if (this.libraryPackages.includes(libraryPackage.name)) {
|
341
|
+
await this.reloadLibrary()
|
342
|
+
} else {
|
343
|
+
await this.reloadUnitManifest(libraryPackage)
|
344
|
+
}
|
345
|
+
|
346
|
+
await this.rebuildLibraryPackageDependents(libraryPackage, rebuiltPackages)
|
347
|
+
}
|
348
|
+
|
349
|
+
private async updateLibraryPackageDependencies(libraryPackage: LibraryPackage): Promise<void> {
|
350
|
+
const packageJson = await readPackageJSON(libraryPackage.rootPath)
|
351
|
+
const parsedName = LocalLibraryBackend.parseDependencyName(libraryPackage.name)
|
352
|
+
|
353
|
+
const dependencyPackageNames = pipe(
|
354
|
+
[packageJson.dependencies, packageJson.devDependencies, packageJson.peerDependencies],
|
355
|
+
flatMap(deps => Object.keys(deps ?? {})),
|
356
|
+
unique(),
|
357
|
+
map(LocalLibraryBackend.parseDependencyName),
|
358
|
+
)
|
359
|
+
|
360
|
+
const sameScopeDependencies = dependencyPackageNames.filter(
|
361
|
+
dep => dep.scope === parsedName.scope && dep.name !== parsedName.name,
|
362
|
+
)
|
363
|
+
|
364
|
+
await this.ensureLibraryPackagesLoaded(sameScopeDependencies.map(dep => dep.name))
|
365
|
+
|
366
|
+
for (const dependency of sameScopeDependencies) {
|
367
|
+
const dependencyPackage = this.packages.get(dependency.name)
|
368
|
+
if (!dependencyPackage) {
|
369
|
+
this.logger.warn(`dependency package not found for graph update: "%s"`, dependency.name)
|
370
|
+
continue
|
371
|
+
}
|
372
|
+
|
373
|
+
libraryPackage.dependencies.add(dependency.name)
|
374
|
+
dependencyPackage.dependents.add(libraryPackage.name)
|
375
|
+
}
|
376
|
+
}
|
377
|
+
|
378
|
+
private async rebuildLibraryPackageDependents(
|
379
|
+
libraryPackage: LibraryPackage,
|
380
|
+
rebuiltPackages = new Set<string>(),
|
381
|
+
): Promise<void> {
|
382
|
+
const promises: Promise<void>[] = []
|
383
|
+
|
384
|
+
for (const dependent of libraryPackage.dependents) {
|
385
|
+
const dependentPackage = this.packages.get(dependent)
|
386
|
+
if (!dependentPackage) {
|
387
|
+
this.logger.warn(`dependent package not found for rebuild: "%s"`, dependent)
|
388
|
+
continue
|
389
|
+
}
|
390
|
+
|
391
|
+
promises.push(this.rebuildLibraryPackage(dependentPackage, false, false, rebuiltPackages))
|
392
|
+
}
|
393
|
+
|
394
|
+
await Promise.all(promises)
|
395
|
+
}
|
396
|
+
|
397
|
+
private static parseDependencyName(this: void, dependency: string) {
|
398
|
+
if (dependency.startsWith("@")) {
|
399
|
+
const parts = dependency.split("/")
|
400
|
+
|
401
|
+
return {
|
402
|
+
name: dependency,
|
403
|
+
scope: parts[0],
|
404
|
+
}
|
405
|
+
}
|
406
|
+
|
407
|
+
return {
|
408
|
+
name: dependency,
|
409
|
+
scope: null,
|
410
|
+
}
|
411
|
+
}
|
412
|
+
|
413
|
+
private async readLibraryPackageManifest(
|
414
|
+
libraryPackage: LibraryPackage,
|
415
|
+
): Promise<HighstateManifestJson | undefined> {
|
416
|
+
const manifestPath = resolve(libraryPackage.rootPath, "dist", "highstate.manifest.json")
|
417
|
+
|
418
|
+
try {
|
419
|
+
const manifest = JSON.parse(await readFile(manifestPath, "utf8")) as HighstateManifestJson
|
420
|
+
|
421
|
+
return manifest
|
422
|
+
} catch (error) {
|
423
|
+
this.logger.debug(
|
424
|
+
{ error },
|
425
|
+
`failed to read highstate manifest of package: "%s"`,
|
426
|
+
libraryPackage.name,
|
427
|
+
)
|
428
|
+
|
429
|
+
return undefined
|
430
|
+
}
|
431
|
+
}
|
432
|
+
|
433
|
+
private async loadLibraryPackages(names: string[], installIfNotFound = false): Promise<void> {
|
434
|
+
this.logger.info("loading library packages: %s", names.join(", "))
|
435
|
+
|
436
|
+
const missingPackages: string[] = []
|
437
|
+
const packagesToUpdate: LibraryPackage[] = []
|
438
|
+
|
439
|
+
const worker = this.createPackageResolutionWorker({ packageNames: names })
|
440
|
+
for await (const [event] of on(worker, "message")) {
|
441
|
+
const eventData = event as PackageResolutionResponse
|
442
|
+
|
443
|
+
if (eventData.type !== "result") {
|
444
|
+
continue
|
445
|
+
}
|
446
|
+
|
447
|
+
for (const result of eventData.results) {
|
448
|
+
if (result.type === "success") {
|
449
|
+
const libraryPackage: LibraryPackage = {
|
450
|
+
name: result.packageName,
|
451
|
+
rootPath: result.packageRootPath,
|
452
|
+
dependencies: new Set(),
|
453
|
+
dependents: new Set(),
|
454
|
+
}
|
455
|
+
|
456
|
+
this.packages.set(result.packageName, libraryPackage)
|
457
|
+
packagesToUpdate.push(libraryPackage)
|
458
|
+
|
459
|
+
this.logger.info(`loaded library package: "%s"`, result.packageName)
|
460
|
+
} else if (result.type === "not-found") {
|
461
|
+
missingPackages.push(result.packageName)
|
462
|
+
} else {
|
463
|
+
this.logger.error(
|
464
|
+
`failed to load library package "%s": %s`,
|
465
|
+
result.packageName,
|
466
|
+
result.error,
|
467
|
+
)
|
468
|
+
}
|
384
469
|
}
|
385
470
|
|
386
|
-
|
471
|
+
break
|
387
472
|
}
|
388
473
|
|
389
|
-
|
390
|
-
|
391
|
-
|
474
|
+
for (const libraryPackage of packagesToUpdate) {
|
475
|
+
await this.updateLibraryPackageDependencies(libraryPackage)
|
476
|
+
|
477
|
+
if (!this.libraryPackages.includes(libraryPackage.name)) {
|
478
|
+
await this.reloadUnitManifest(libraryPackage)
|
479
|
+
}
|
480
|
+
}
|
481
|
+
|
482
|
+
if (installIfNotFound && missingPackages.length > 0) {
|
483
|
+
this.logger.info("installing missing library packages: %s", missingPackages.join(", "))
|
484
|
+
await addDependency(missingPackages)
|
485
|
+
await this.loadLibraryPackages(missingPackages)
|
486
|
+
}
|
487
|
+
}
|
488
|
+
|
489
|
+
private async handleFileEvent(path: string): Promise<void> {
|
490
|
+
await this.lock.acquire(async () => {
|
491
|
+
const libraryPackage = this.packages.values().find(pkg => path.startsWith(pkg.rootPath))
|
492
|
+
|
493
|
+
if (libraryPackage) {
|
494
|
+
await this.rebuildLibraryPackage(libraryPackage)
|
495
|
+
}
|
496
|
+
})
|
497
|
+
}
|
498
|
+
|
499
|
+
private createLibraryWorker(workerData: Record<string, unknown>) {
|
500
|
+
const workerPathUrl = importMetaResolve(`@highstate/backend/library-worker`, import.meta.url)
|
501
|
+
const workerPath = fileURLToPath(workerPathUrl)
|
502
|
+
|
503
|
+
return new Worker(workerPath, { workerData })
|
504
|
+
}
|
505
|
+
|
506
|
+
private createPackageResolutionWorker(workerData: PackageResolutionWorkerData) {
|
507
|
+
const workerPathUrl = importMetaResolve(
|
508
|
+
`@highstate/backend/package-resolution-worker`,
|
509
|
+
import.meta.url,
|
510
|
+
)
|
511
|
+
const workerPath = fileURLToPath(workerPathUrl)
|
512
|
+
|
513
|
+
return new Worker(workerPath, { workerData })
|
514
|
+
}
|
515
|
+
|
516
|
+
static async create(config: z.infer<typeof localLibraryBackendConfig>, logger: Logger) {
|
517
|
+
let watchPaths = config.HIGHSTATE_BACKEND_LIBRARY_LOCAL_WATCH_PATHS
|
518
|
+
if (!watchPaths) {
|
392
519
|
const [projectPath] = await resolveMainLocalProject()
|
393
|
-
|
394
|
-
extraSourceWatchPaths.push(projectPath)
|
520
|
+
watchPaths = [resolve(projectPath, "packages")]
|
395
521
|
}
|
396
522
|
|
397
523
|
return new LocalLibraryBackend(
|
398
|
-
|
399
|
-
|
400
|
-
extraSourceWatchPaths,
|
524
|
+
config.HIGHSTATE_BACKEND_LIBRARY_LOCAL_PACKAGES,
|
525
|
+
watchPaths,
|
401
526
|
logger.child({ backend: "LibraryBackend", service: "LocalLibraryBackend" }),
|
402
527
|
)
|
403
528
|
}
|
@@ -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) {
|