@highstate/cli 0.9.14 → 0.9.16
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 +1 -1
- package/dist/library-loader-CGEPTS4L.js +78 -0
- package/dist/library-loader-CGEPTS4L.js.map +1 -0
- package/dist/main.js +257 -1096
- package/dist/main.js.map +1 -1
- package/package.json +27 -6
- package/src/commands/backend/identity.ts +24 -0
- package/src/commands/build.ts +41 -5
- package/src/main.ts +2 -0
- package/src/shared/index.ts +1 -0
- package/src/shared/library-loader.ts +113 -0
- package/src/shared/schema-transformer.spec.ts +209 -0
- package/src/shared/schema-transformer.ts +112 -11
- package/src/shared/schemas.ts +41 -0
- package/src/shared/source-hash-calculator.ts +129 -26
|
@@ -3,12 +3,18 @@ import { dirname, relative, resolve } from "node:path"
|
|
|
3
3
|
import { readFile, writeFile } from "node:fs/promises"
|
|
4
4
|
import { fileURLToPath, pathToFileURL } from "node:url"
|
|
5
5
|
import { readPackageJSON, resolvePackageJSON, type PackageJson } from "pkg-types"
|
|
6
|
-
import {
|
|
6
|
+
import { crc32 } from "@aws-crypto/crc32"
|
|
7
7
|
import { resolve as importMetaResolve } from "import-meta-resolve"
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
import { z } from "zod"
|
|
9
|
+
import { int32ToBytes } from "@highstate/backend/shared"
|
|
10
|
+
import {
|
|
11
|
+
type HighstateManifest,
|
|
12
|
+
type HighstateConfig,
|
|
13
|
+
type SourceHashConfig,
|
|
14
|
+
highstateConfigSchema,
|
|
15
|
+
highstateManifestSchema,
|
|
16
|
+
sourceHashConfigSchema,
|
|
17
|
+
} from "./schemas"
|
|
12
18
|
|
|
13
19
|
type FileDependency =
|
|
14
20
|
| {
|
|
@@ -23,8 +29,8 @@ type FileDependency =
|
|
|
23
29
|
}
|
|
24
30
|
|
|
25
31
|
export class SourceHashCalculator {
|
|
26
|
-
private readonly dependencyHashes = new Map<string, Promise<
|
|
27
|
-
private readonly fileHashes = new Map<string, Promise<
|
|
32
|
+
private readonly dependencyHashes = new Map<string, Promise<number>>()
|
|
33
|
+
private readonly fileHashes = new Map<string, Promise<number>>()
|
|
28
34
|
|
|
29
35
|
constructor(
|
|
30
36
|
private readonly packageJsonPath: string,
|
|
@@ -32,21 +38,117 @@ export class SourceHashCalculator {
|
|
|
32
38
|
private readonly logger: Logger,
|
|
33
39
|
) {}
|
|
34
40
|
|
|
35
|
-
|
|
36
|
-
|
|
41
|
+
/**
|
|
42
|
+
* Calculates CRC32 hash of a string.
|
|
43
|
+
*/
|
|
44
|
+
private hashString(input: string): number {
|
|
45
|
+
return crc32(Buffer.from(input))
|
|
46
|
+
}
|
|
37
47
|
|
|
38
|
-
|
|
39
|
-
|
|
48
|
+
/**
|
|
49
|
+
* Gets the highstate configuration from package.json with defaults.
|
|
50
|
+
*/
|
|
51
|
+
private getHighstateConfig(packageJson: PackageJson): HighstateConfig {
|
|
52
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
53
|
+
const rawConfig = packageJson.highstate
|
|
54
|
+
if (!rawConfig) {
|
|
55
|
+
return { type: "source" }
|
|
56
|
+
}
|
|
40
57
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
58
|
+
try {
|
|
59
|
+
return highstateConfigSchema.parse(rawConfig)
|
|
60
|
+
} catch (error) {
|
|
61
|
+
this.logger.warn(
|
|
62
|
+
{ error, packageName: packageJson.name },
|
|
63
|
+
"invalid highstate configuration, using defaults",
|
|
46
64
|
)
|
|
65
|
+
return { type: "source" }
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Gets the effective source hash configuration with defaults for a specific output.
|
|
71
|
+
*/
|
|
72
|
+
private getSourceHashConfig(
|
|
73
|
+
highstateConfig: HighstateConfig,
|
|
74
|
+
exportKey?: string,
|
|
75
|
+
): SourceHashConfig {
|
|
76
|
+
if (highstateConfig.sourceHash) {
|
|
77
|
+
// Try to parse as a single config first
|
|
78
|
+
const singleConfigResult = sourceHashConfigSchema.safeParse(highstateConfig.sourceHash)
|
|
79
|
+
if (singleConfigResult.success) {
|
|
80
|
+
return singleConfigResult.data
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Try to parse as a record of configs
|
|
84
|
+
const recordConfigResult = z
|
|
85
|
+
.record(z.string(), sourceHashConfigSchema)
|
|
86
|
+
.safeParse(highstateConfig.sourceHash)
|
|
87
|
+
if (recordConfigResult.success && exportKey) {
|
|
88
|
+
const perOutputConfig = recordConfigResult.data[exportKey]
|
|
89
|
+
if (perOutputConfig) {
|
|
90
|
+
return perOutputConfig
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (highstateConfig.type === "library") {
|
|
96
|
+
return { mode: "none" }
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return { mode: "auto" }
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async writeHighstateManifest(
|
|
103
|
+
distBasePath: string,
|
|
104
|
+
distPathToExportKey: Map<string, string>,
|
|
105
|
+
): Promise<void> {
|
|
106
|
+
const highstateConfig = this.getHighstateConfig(this.packageJson)
|
|
107
|
+
|
|
108
|
+
const promises: Promise<{ distPath: string; hash: number }>[] = []
|
|
109
|
+
|
|
110
|
+
for (const [distPath, exportKey] of distPathToExportKey) {
|
|
111
|
+
const fullPath = resolve(distPath)
|
|
112
|
+
const sourceHashConfig = this.getSourceHashConfig(highstateConfig, exportKey)
|
|
113
|
+
|
|
114
|
+
switch (sourceHashConfig.mode) {
|
|
115
|
+
case "manual":
|
|
116
|
+
promises.push(
|
|
117
|
+
Promise.resolve({
|
|
118
|
+
distPath,
|
|
119
|
+
hash: this.hashString(sourceHashConfig.version),
|
|
120
|
+
}),
|
|
121
|
+
)
|
|
122
|
+
break
|
|
123
|
+
case "version":
|
|
124
|
+
promises.push(
|
|
125
|
+
Promise.resolve({
|
|
126
|
+
distPath,
|
|
127
|
+
hash: this.hashString(this.packageJson.version ?? ""),
|
|
128
|
+
}),
|
|
129
|
+
)
|
|
130
|
+
break
|
|
131
|
+
case "none":
|
|
132
|
+
promises.push(
|
|
133
|
+
Promise.resolve({
|
|
134
|
+
distPath,
|
|
135
|
+
hash: 0,
|
|
136
|
+
}),
|
|
137
|
+
)
|
|
138
|
+
break
|
|
139
|
+
case "auto":
|
|
140
|
+
default:
|
|
141
|
+
promises.push(
|
|
142
|
+
this.getFileHash(fullPath).then(hash => ({
|
|
143
|
+
distPath,
|
|
144
|
+
hash,
|
|
145
|
+
})),
|
|
146
|
+
)
|
|
147
|
+
break
|
|
148
|
+
}
|
|
47
149
|
}
|
|
48
150
|
|
|
49
|
-
const manifest:
|
|
151
|
+
const manifest: HighstateManifest = {
|
|
50
152
|
sourceHashes: {},
|
|
51
153
|
}
|
|
52
154
|
|
|
@@ -59,7 +161,7 @@ export class SourceHashCalculator {
|
|
|
59
161
|
await writeFile(manifestPath, JSON.stringify(manifest, null, 2), "utf8")
|
|
60
162
|
}
|
|
61
163
|
|
|
62
|
-
private async getFileHash(fullPath: string): Promise<
|
|
164
|
+
private async getFileHash(fullPath: string): Promise<number> {
|
|
63
165
|
const existingHash = this.fileHashes.get(fullPath)
|
|
64
166
|
if (existingHash) {
|
|
65
167
|
return existingHash
|
|
@@ -71,19 +173,19 @@ export class SourceHashCalculator {
|
|
|
71
173
|
return hash
|
|
72
174
|
}
|
|
73
175
|
|
|
74
|
-
private async calculateFileHash(fullPath: string): Promise<
|
|
176
|
+
private async calculateFileHash(fullPath: string): Promise<number> {
|
|
75
177
|
const content = await readFile(fullPath, "utf8")
|
|
76
178
|
const fileDeps = this.parseDependencies(fullPath, content)
|
|
77
179
|
|
|
78
180
|
const hashes = await Promise.all([
|
|
79
|
-
|
|
181
|
+
this.hashString(content),
|
|
80
182
|
...fileDeps.map(dep => this.getDependencyHash(dep)),
|
|
81
183
|
])
|
|
82
184
|
|
|
83
|
-
return
|
|
185
|
+
return crc32(Buffer.concat(hashes.map(int32ToBytes)))
|
|
84
186
|
}
|
|
85
187
|
|
|
86
|
-
getDependencyHash(dependency: FileDependency): Promise<
|
|
188
|
+
getDependencyHash(dependency: FileDependency): Promise<number> {
|
|
87
189
|
const existingHash = this.dependencyHashes.get(dependency.id)
|
|
88
190
|
if (existingHash) {
|
|
89
191
|
return existingHash
|
|
@@ -95,7 +197,7 @@ export class SourceHashCalculator {
|
|
|
95
197
|
return hash
|
|
96
198
|
}
|
|
97
199
|
|
|
98
|
-
private async calculateDependencyHash(dependency: FileDependency): Promise<
|
|
200
|
+
private async calculateDependencyHash(dependency: FileDependency): Promise<number> {
|
|
99
201
|
switch (dependency.type) {
|
|
100
202
|
case "relative": {
|
|
101
203
|
return await this.getFileHash(dependency.fullPath)
|
|
@@ -133,6 +235,7 @@ export class SourceHashCalculator {
|
|
|
133
235
|
this.logger.warn(`package "%s" is not listed in package.json dependencies`, packageName)
|
|
134
236
|
}
|
|
135
237
|
|
|
238
|
+
// try to get source hash from manifest first
|
|
136
239
|
let relativePath = relative(dirname(depPackageJsonPath), resolvedPath)
|
|
137
240
|
relativePath = relativePath.startsWith(".") ? relativePath : `./${relativePath}`
|
|
138
241
|
|
|
@@ -142,10 +245,10 @@ export class SourceHashCalculator {
|
|
|
142
245
|
"highstate.manifest.json",
|
|
143
246
|
)
|
|
144
247
|
|
|
145
|
-
let manifest:
|
|
248
|
+
let manifest: HighstateManifest | undefined
|
|
146
249
|
try {
|
|
147
250
|
const manifestContent = await readFile(highstateManifestPath, "utf8")
|
|
148
|
-
manifest = JSON.parse(manifestContent)
|
|
251
|
+
manifest = highstateManifestSchema.parse(JSON.parse(manifestContent))
|
|
149
252
|
} catch (error) {
|
|
150
253
|
this.logger.debug(
|
|
151
254
|
{ error },
|
|
@@ -164,7 +267,7 @@ export class SourceHashCalculator {
|
|
|
164
267
|
// use the package version as a fallback hash
|
|
165
268
|
// this case will be applied for most npm packages
|
|
166
269
|
this.logger.debug(`using package version as a fallback hash for "%s"`, packageName)
|
|
167
|
-
return depPackageJson.version ?? "0.0.0"
|
|
270
|
+
return this.hashString(depPackageJson.version ?? "0.0.0")
|
|
168
271
|
}
|
|
169
272
|
}
|
|
170
273
|
}
|