@highstate/cli 0.7.1 → 0.7.3
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/assets/tsconfig.base.json +21 -0
- package/dist/main.js +1285 -13978
- package/package.json +18 -8
- package/src/commands/build.ts +94 -0
- package/src/commands/designer.ts +73 -0
- package/src/main.ts +17 -0
- package/src/shared/index.ts +4 -0
- package/src/shared/logger.ts +54 -0
- package/src/shared/schema-transformer.ts +75 -0
- package/src/shared/services.ts +20 -0
- package/src/shared/source-hash-calculator.ts +219 -0
- package/dist/index-CfX7oR0h.js +0 -215
- package/dist/main-fVI30_8j.js +0 -520
package/package.json
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
{
|
2
2
|
"name": "@highstate/cli",
|
3
|
-
"version": "0.7.
|
3
|
+
"version": "0.7.3",
|
4
4
|
"description": "The CLI for the Highstate project.",
|
5
5
|
"type": "module",
|
6
6
|
"files": [
|
7
|
-
"
|
7
|
+
"assets",
|
8
|
+
"dist",
|
9
|
+
"src"
|
8
10
|
],
|
9
11
|
"bin": {
|
10
12
|
"highstate": "dist/main.js"
|
@@ -13,19 +15,27 @@
|
|
13
15
|
"access": "public"
|
14
16
|
},
|
15
17
|
"scripts": {
|
16
|
-
"build": "pkgroll --tsconfig=tsconfig.build.json"
|
18
|
+
"build": "pkgroll --clean --tsconfig=tsconfig.build.json"
|
17
19
|
},
|
18
20
|
"dependencies": {
|
19
|
-
"@highstate/backend": "^0.7.1",
|
20
|
-
"consola": "^3.4.0"
|
21
|
-
},
|
22
|
-
"devDependencies": {
|
23
21
|
"clipanion": "^4.0.0-rc.4",
|
22
|
+
"consola": "^3.4.0",
|
23
|
+
"crypto-hash": "^3.1.0",
|
24
24
|
"get-port-please": "^3.1.2",
|
25
|
+
"import-meta-resolve": "^4.1.0",
|
25
26
|
"nypm": "^0.4.1",
|
27
|
+
"oxc-parser": "^0.63.0",
|
28
|
+
"oxc-walker": "^0.2.4",
|
26
29
|
"pino": "^9.6.0",
|
27
30
|
"pkg-types": "^1.2.1",
|
31
|
+
"remeda": "^2.21.2",
|
32
|
+
"tsup": "^8.4.0"
|
33
|
+
},
|
34
|
+
"devDependencies": {
|
28
35
|
"pkgroll": "^2.5.1"
|
29
36
|
},
|
30
|
-
"
|
37
|
+
"peerDependencies": {
|
38
|
+
"@highstate/backend": "workspace:^0.7.2"
|
39
|
+
},
|
40
|
+
"gitHead": "5cf7cec27262c8fa1d96f6478833b94841459d64"
|
31
41
|
}
|
@@ -0,0 +1,94 @@
|
|
1
|
+
import { Command, Option } from "clipanion"
|
2
|
+
import { readPackageJSON } from "pkg-types"
|
3
|
+
import { mapKeys, mapValues, pipe } from "remeda"
|
4
|
+
import { build } from "tsup"
|
5
|
+
import { logger, schemaTransformerPlugin, SourceHashCalculator } from "../shared"
|
6
|
+
|
7
|
+
export class BuildCommand extends Command {
|
8
|
+
static paths = [["build"]]
|
9
|
+
|
10
|
+
static usage = Command.Usage({
|
11
|
+
category: "Builder",
|
12
|
+
description: "Builds the Highstate library or unit package.",
|
13
|
+
})
|
14
|
+
|
15
|
+
watch = Option.Boolean("--watch", false)
|
16
|
+
library = Option.Boolean("--library", false)
|
17
|
+
|
18
|
+
async execute(): Promise<void> {
|
19
|
+
const packageJson = await readPackageJSON()
|
20
|
+
const exports = packageJson.exports
|
21
|
+
|
22
|
+
if (!exports) {
|
23
|
+
logger.warn("no exports found in package.json")
|
24
|
+
return
|
25
|
+
}
|
26
|
+
|
27
|
+
if (typeof exports !== "object" || Array.isArray(exports)) {
|
28
|
+
throw new Error("Exports field in package.json must be an object")
|
29
|
+
}
|
30
|
+
|
31
|
+
const entry = pipe(
|
32
|
+
exports,
|
33
|
+
mapValues((value, key) => {
|
34
|
+
let distPath
|
35
|
+
|
36
|
+
if (typeof value === "string") {
|
37
|
+
distPath = value
|
38
|
+
} else if (typeof value === "object" && !Array.isArray(value)) {
|
39
|
+
if (!value.default) {
|
40
|
+
throw new Error(`Export "${key}" must have a default field in package.json`)
|
41
|
+
}
|
42
|
+
|
43
|
+
if (typeof value.default !== "string") {
|
44
|
+
throw new Error(`Export "${key}" default field must be a string in package.json`)
|
45
|
+
}
|
46
|
+
|
47
|
+
distPath = value.default
|
48
|
+
} else {
|
49
|
+
throw new Error(`Export "${key}" must be a string or an object in package.json`)
|
50
|
+
}
|
51
|
+
|
52
|
+
if (!distPath.startsWith("./dist/")) {
|
53
|
+
throw new Error(
|
54
|
+
`The default value of export "${key}" must start with "./dist/" in package.json, got "${distPath}"`,
|
55
|
+
)
|
56
|
+
}
|
57
|
+
|
58
|
+
if (!distPath.endsWith(".js")) {
|
59
|
+
throw new Error(
|
60
|
+
`The default value of export "${key}" must end with ".js" in package.json, got "${distPath}"`,
|
61
|
+
)
|
62
|
+
}
|
63
|
+
|
64
|
+
const targetName = distPath.slice(7).slice(0, -3)
|
65
|
+
|
66
|
+
return {
|
67
|
+
entryPoint: `./src/${targetName}.ts`,
|
68
|
+
targetName,
|
69
|
+
distPath,
|
70
|
+
}
|
71
|
+
}),
|
72
|
+
mapKeys((_, value) => value.targetName),
|
73
|
+
)
|
74
|
+
|
75
|
+
await build({
|
76
|
+
entry: mapValues(entry, value => value.entryPoint),
|
77
|
+
outDir: "dist",
|
78
|
+
watch: this.watch,
|
79
|
+
sourcemap: true,
|
80
|
+
clean: true,
|
81
|
+
format: "esm",
|
82
|
+
target: "esnext",
|
83
|
+
external: ["@pulumi/pulumi"],
|
84
|
+
esbuildPlugins: this.library ? [schemaTransformerPlugin] : [],
|
85
|
+
})
|
86
|
+
|
87
|
+
const upToDatePackageJson = await readPackageJSON()
|
88
|
+
|
89
|
+
const sourceHashCalculator = new SourceHashCalculator(upToDatePackageJson, logger)
|
90
|
+
const distPaths = Object.values(entry).map(value => value.distPath)
|
91
|
+
|
92
|
+
await sourceHashCalculator.writeHighstateManifest("./dist", distPaths)
|
93
|
+
}
|
94
|
+
}
|
@@ -0,0 +1,73 @@
|
|
1
|
+
import { Command, UsageError } from "clipanion"
|
2
|
+
import { readPackageJSON } from "pkg-types"
|
3
|
+
import { addDevDependency } from "nypm"
|
4
|
+
import { consola } from "consola"
|
5
|
+
import { colorize } from "consola/utils"
|
6
|
+
import { getPort } from "get-port-please"
|
7
|
+
import { getBackendServices, logger } from "../shared"
|
8
|
+
|
9
|
+
export class DesignerCommand extends Command {
|
10
|
+
static paths = [["designer"]]
|
11
|
+
|
12
|
+
static usage = Command.Usage({
|
13
|
+
category: "Designer",
|
14
|
+
description: "Starts the Highstate designer in the current project.",
|
15
|
+
})
|
16
|
+
|
17
|
+
async execute(): Promise<void> {
|
18
|
+
const packageJson = await readPackageJSON()
|
19
|
+
if (!packageJson.devDependencies?.["@highstate/cli"]) {
|
20
|
+
throw new UsageError(
|
21
|
+
"This project is not a Highstate project.\n@highstate/cli must be installed as a devDependency.",
|
22
|
+
)
|
23
|
+
}
|
24
|
+
|
25
|
+
if (!packageJson.devDependencies?.["@highstate/designer"]) {
|
26
|
+
logger.info("Installing @highstate/designer...")
|
27
|
+
|
28
|
+
await addDevDependency(["@highstate/designer", "classic-level"])
|
29
|
+
}
|
30
|
+
|
31
|
+
logger.info("starting highstate designer...")
|
32
|
+
|
33
|
+
await getBackendServices()
|
34
|
+
|
35
|
+
const oldConsoleLog = console.log
|
36
|
+
|
37
|
+
const port = await getPort()
|
38
|
+
|
39
|
+
process.env.NITRO_PORT = port.toString()
|
40
|
+
process.env.NITRO_HOST = "0.0.0.0"
|
41
|
+
|
42
|
+
await new Promise<void>(resolve => {
|
43
|
+
console.log = (message: string) => {
|
44
|
+
if (message.startsWith("Listening on")) {
|
45
|
+
resolve()
|
46
|
+
}
|
47
|
+
}
|
48
|
+
|
49
|
+
const path = "@highstate/designer/.output/server/index.mjs"
|
50
|
+
void import(path)
|
51
|
+
})
|
52
|
+
|
53
|
+
console.log = oldConsoleLog
|
54
|
+
|
55
|
+
consola.log(
|
56
|
+
[
|
57
|
+
"\n ",
|
58
|
+
colorize("bold", colorize("cyanBright", "Highstate Designer")),
|
59
|
+
"\n ",
|
60
|
+
colorize("greenBright", "➜ Local: "),
|
61
|
+
colorize("underline", colorize("cyanBright", `http://localhost:${port}`)),
|
62
|
+
"\n",
|
63
|
+
].join(""),
|
64
|
+
)
|
65
|
+
|
66
|
+
process.on("SIGINT", () => {
|
67
|
+
process.stdout.write("\r")
|
68
|
+
consola.info("shutting down highstate designer...")
|
69
|
+
|
70
|
+
setTimeout(() => process.exit(0), 1000)
|
71
|
+
})
|
72
|
+
}
|
73
|
+
}
|
package/src/main.ts
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
import { Builtins, Cli } from "clipanion"
|
2
|
+
import { version } from "../package.json"
|
3
|
+
import { DesignerCommand } from "./commands/designer"
|
4
|
+
import { BuildCommand } from "./commands/build"
|
5
|
+
|
6
|
+
const cli = new Cli({
|
7
|
+
binaryName: "highstate",
|
8
|
+
binaryLabel: "Highstate",
|
9
|
+
binaryVersion: version,
|
10
|
+
})
|
11
|
+
|
12
|
+
cli.register(BuildCommand)
|
13
|
+
cli.register(DesignerCommand)
|
14
|
+
cli.register(Builtins.HelpCommand)
|
15
|
+
cli.register(Builtins.VersionCommand)
|
16
|
+
|
17
|
+
await cli.runExit(process.argv.slice(2))
|
@@ -0,0 +1,54 @@
|
|
1
|
+
import { PassThrough } from "node:stream"
|
2
|
+
import pino, { levels } from "pino"
|
3
|
+
import { consola, LogLevels } from "consola"
|
4
|
+
|
5
|
+
export const logger = pino(
|
6
|
+
{
|
7
|
+
name: "highstate-cli",
|
8
|
+
level: process.env.LOG_LEVEL ?? "info",
|
9
|
+
},
|
10
|
+
createConsolaStream(),
|
11
|
+
)
|
12
|
+
|
13
|
+
consola.level = LogLevels[(process.env.LOG_LEVEL as keyof typeof LogLevels) ?? "info"]
|
14
|
+
|
15
|
+
function createConsolaStream() {
|
16
|
+
const stream = new PassThrough()
|
17
|
+
|
18
|
+
stream.on("data", data => {
|
19
|
+
const { level, msg, error } = JSON.parse(String(data)) as {
|
20
|
+
msg: string
|
21
|
+
level: number
|
22
|
+
error?: unknown
|
23
|
+
}
|
24
|
+
|
25
|
+
const levelLabel = levels.labels[level]
|
26
|
+
|
27
|
+
switch (levelLabel) {
|
28
|
+
case "info":
|
29
|
+
consola.info(msg)
|
30
|
+
break
|
31
|
+
case "warn":
|
32
|
+
consola.warn(msg)
|
33
|
+
break
|
34
|
+
case "error":
|
35
|
+
if (error) {
|
36
|
+
consola.error(msg, error)
|
37
|
+
} else {
|
38
|
+
consola.error(msg)
|
39
|
+
}
|
40
|
+
break
|
41
|
+
case "debug":
|
42
|
+
consola.debug(msg)
|
43
|
+
break
|
44
|
+
case "fatal":
|
45
|
+
consola.fatal(msg)
|
46
|
+
break
|
47
|
+
case "trace":
|
48
|
+
consola.trace(msg)
|
49
|
+
break
|
50
|
+
}
|
51
|
+
})
|
52
|
+
|
53
|
+
return stream
|
54
|
+
}
|
@@ -0,0 +1,75 @@
|
|
1
|
+
import type { Plugin } from "esbuild"
|
2
|
+
import { readFile } from "node:fs/promises"
|
3
|
+
import { parseAsync, type Comment } from "oxc-parser"
|
4
|
+
import { walk, type Node } from "oxc-walker"
|
5
|
+
import MagicString from "magic-string"
|
6
|
+
|
7
|
+
export const schemaTransformerPlugin: Plugin = {
|
8
|
+
name: "schema-transformer",
|
9
|
+
setup(build) {
|
10
|
+
build.onLoad({ filter: /src\/.*\.ts$/ }, async args => {
|
11
|
+
const content = await readFile(args.path, "utf-8")
|
12
|
+
|
13
|
+
return {
|
14
|
+
contents: await applySchemaTransformations(content),
|
15
|
+
loader: "ts",
|
16
|
+
}
|
17
|
+
})
|
18
|
+
},
|
19
|
+
}
|
20
|
+
|
21
|
+
export async function applySchemaTransformations(content: string): Promise<string> {
|
22
|
+
const magicString = new MagicString(content)
|
23
|
+
const { program, comments } = await parseAsync("file.ts", content)
|
24
|
+
|
25
|
+
walk(program, {
|
26
|
+
enter(node) {
|
27
|
+
if (node.type !== "Property" || node.key.type !== "Identifier") {
|
28
|
+
return
|
29
|
+
}
|
30
|
+
|
31
|
+
const jsdoc = comments.find(comment => isLeadingComment(content, node, comment))
|
32
|
+
if (!jsdoc || !jsdoc.value.includes("@schema")) {
|
33
|
+
return
|
34
|
+
}
|
35
|
+
|
36
|
+
magicString.update(
|
37
|
+
node.value.start,
|
38
|
+
node.value.end,
|
39
|
+
`{
|
40
|
+
...${content.substring(node.value.start, node.value.end)},
|
41
|
+
description: \`${cleanJsdoc(jsdoc.value)}\`,
|
42
|
+
}`,
|
43
|
+
)
|
44
|
+
},
|
45
|
+
})
|
46
|
+
|
47
|
+
return magicString.toString()
|
48
|
+
}
|
49
|
+
|
50
|
+
function isLeadingComment(content: string, node: Node, comment: Comment) {
|
51
|
+
if (comment.end > node.start) {
|
52
|
+
return false
|
53
|
+
}
|
54
|
+
|
55
|
+
const contentRange = content.substring(comment.end, node.start)
|
56
|
+
|
57
|
+
return contentRange.trim().length === 0
|
58
|
+
}
|
59
|
+
|
60
|
+
function cleanJsdoc(str: string) {
|
61
|
+
return (
|
62
|
+
str
|
63
|
+
// remove leading asterisks
|
64
|
+
.replace(/^\s*\*/gm, "")
|
65
|
+
|
66
|
+
// remove @schema tag
|
67
|
+
.replace("@schema", "")
|
68
|
+
|
69
|
+
// escape backticks and dollar signs
|
70
|
+
.replace(/\\/g, "\\\\")
|
71
|
+
.replace(/`/g, "\\`")
|
72
|
+
.replace(/\${/g, "\\${")
|
73
|
+
.trim()
|
74
|
+
)
|
75
|
+
}
|
@@ -0,0 +1,20 @@
|
|
1
|
+
import type { Services } from "@highstate/backend"
|
2
|
+
import { logger } from "./logger"
|
3
|
+
|
4
|
+
let services: Promise<Services> | undefined
|
5
|
+
|
6
|
+
export function getBackendServices() {
|
7
|
+
if (services) {
|
8
|
+
return services
|
9
|
+
}
|
10
|
+
|
11
|
+
services = import("@highstate/backend").then(({ getSharedServices }) => {
|
12
|
+
return getSharedServices({
|
13
|
+
services: {
|
14
|
+
logger: logger.child({}, { msgPrefix: "[backend] " }),
|
15
|
+
},
|
16
|
+
})
|
17
|
+
})
|
18
|
+
|
19
|
+
return services
|
20
|
+
}
|
@@ -0,0 +1,219 @@
|
|
1
|
+
import type { Logger } from "pino"
|
2
|
+
import { dirname, relative, resolve } from "node:path"
|
3
|
+
import { readFile, writeFile } from "node:fs/promises"
|
4
|
+
import { fileURLToPath } from "node:url"
|
5
|
+
import { readPackageJSON, resolvePackageJSON, type PackageJson } from "pkg-types"
|
6
|
+
import { sha256 } from "crypto-hash"
|
7
|
+
import { resolve as importMetaResolve } from "import-meta-resolve"
|
8
|
+
|
9
|
+
export type HighstateManifestJson = {
|
10
|
+
sourceHashes?: Record<string, string>
|
11
|
+
}
|
12
|
+
|
13
|
+
type FileDependency =
|
14
|
+
| {
|
15
|
+
type: "relative"
|
16
|
+
id: string
|
17
|
+
fullPath: string
|
18
|
+
}
|
19
|
+
| {
|
20
|
+
type: "npm"
|
21
|
+
id: string
|
22
|
+
package: string
|
23
|
+
}
|
24
|
+
|
25
|
+
export class SourceHashCalculator {
|
26
|
+
private readonly dependencyHashes = new Map<string, Promise<string>>()
|
27
|
+
private readonly fileHashes = new Map<string, Promise<string>>()
|
28
|
+
|
29
|
+
constructor(
|
30
|
+
private readonly packageJson: PackageJson,
|
31
|
+
private readonly logger: Logger,
|
32
|
+
) {}
|
33
|
+
|
34
|
+
async writeHighstateManifest(distBasePath: string, distPaths: string[]): Promise<void> {
|
35
|
+
const promises: Promise<{ distPath: string; hash: string }>[] = []
|
36
|
+
|
37
|
+
for (const distPath of distPaths) {
|
38
|
+
const fullPath = resolve(distPath)
|
39
|
+
|
40
|
+
promises.push(
|
41
|
+
this.getFileHash(fullPath).then(hash => ({
|
42
|
+
distPath,
|
43
|
+
hash,
|
44
|
+
})),
|
45
|
+
)
|
46
|
+
}
|
47
|
+
|
48
|
+
const manifest: HighstateManifestJson = {
|
49
|
+
sourceHashes: {},
|
50
|
+
}
|
51
|
+
|
52
|
+
const hashes = await Promise.all(promises)
|
53
|
+
for (const { distPath, hash } of hashes) {
|
54
|
+
manifest.sourceHashes![distPath] = hash
|
55
|
+
}
|
56
|
+
|
57
|
+
const manifestPath = resolve(distBasePath, "highstate.manifest.json")
|
58
|
+
await writeFile(manifestPath, JSON.stringify(manifest, null, 2), "utf8")
|
59
|
+
}
|
60
|
+
|
61
|
+
private async getFileHash(fullPath: string): Promise<string> {
|
62
|
+
const existingHash = this.fileHashes.get(fullPath)
|
63
|
+
if (existingHash) {
|
64
|
+
return existingHash
|
65
|
+
}
|
66
|
+
|
67
|
+
const hash = this.calculateFileHash(fullPath)
|
68
|
+
this.fileHashes.set(fullPath, hash)
|
69
|
+
|
70
|
+
return hash
|
71
|
+
}
|
72
|
+
|
73
|
+
private async calculateFileHash(fullPath: string): Promise<string> {
|
74
|
+
const content = await readFile(fullPath, "utf8")
|
75
|
+
const fileDeps = this.parseDependencies(fullPath, content)
|
76
|
+
|
77
|
+
const hashes = await Promise.all([
|
78
|
+
sha256(content),
|
79
|
+
...fileDeps.map(dep => this.getDependencyHash(dep)),
|
80
|
+
])
|
81
|
+
|
82
|
+
return await sha256(hashes.join(""))
|
83
|
+
}
|
84
|
+
|
85
|
+
getDependencyHash(dependency: FileDependency): Promise<string> {
|
86
|
+
const existingHash = this.dependencyHashes.get(dependency.id)
|
87
|
+
if (existingHash) {
|
88
|
+
return existingHash
|
89
|
+
}
|
90
|
+
|
91
|
+
const hash = this.calculateDependencyHash(dependency)
|
92
|
+
this.dependencyHashes.set(dependency.id, hash)
|
93
|
+
|
94
|
+
return hash
|
95
|
+
}
|
96
|
+
|
97
|
+
private async calculateDependencyHash(dependency: FileDependency): Promise<string> {
|
98
|
+
switch (dependency.type) {
|
99
|
+
case "relative": {
|
100
|
+
return await this.getFileHash(dependency.fullPath)
|
101
|
+
}
|
102
|
+
case "npm": {
|
103
|
+
let resolvedUrl
|
104
|
+
try {
|
105
|
+
resolvedUrl = importMetaResolve(dependency.package, import.meta.url)
|
106
|
+
} catch (error) {
|
107
|
+
this.logger.error(`failed to resolve package "%s"`, dependency.package)
|
108
|
+
throw error
|
109
|
+
}
|
110
|
+
|
111
|
+
if (resolvedUrl.startsWith("node:")) {
|
112
|
+
throw new Error(`"${dependency.package}" imported without "node:" prefix`)
|
113
|
+
}
|
114
|
+
|
115
|
+
const resolvedPath = fileURLToPath(resolvedUrl)
|
116
|
+
|
117
|
+
const [depPackageJsonPath, depPackageJson] = await this.getPackageJson(resolvedPath)
|
118
|
+
const packageName = depPackageJson.name!
|
119
|
+
|
120
|
+
this.logger.debug(
|
121
|
+
`resolved package.json for "%s": "%s"`,
|
122
|
+
dependency.package,
|
123
|
+
depPackageJsonPath,
|
124
|
+
)
|
125
|
+
|
126
|
+
if (
|
127
|
+
!this.packageJson.dependencies?.[packageName] &&
|
128
|
+
!this.packageJson.peerDependencies?.[packageName]
|
129
|
+
) {
|
130
|
+
this.logger.warn(`package "%s" is not listed in package.json dependencies`, packageName)
|
131
|
+
}
|
132
|
+
|
133
|
+
let relativePath = relative(dirname(depPackageJsonPath), resolvedPath)
|
134
|
+
relativePath = relativePath.startsWith(".") ? relativePath : `./${relativePath}`
|
135
|
+
|
136
|
+
const highstateManifestPath = resolve(
|
137
|
+
dirname(depPackageJsonPath),
|
138
|
+
"dist",
|
139
|
+
"highstate.manifest.json",
|
140
|
+
)
|
141
|
+
|
142
|
+
let manifest: HighstateManifestJson | undefined
|
143
|
+
try {
|
144
|
+
const manifestContent = await readFile(highstateManifestPath, "utf8")
|
145
|
+
manifest = JSON.parse(manifestContent) as HighstateManifestJson
|
146
|
+
} catch (error) {
|
147
|
+
this.logger.debug(
|
148
|
+
{ error },
|
149
|
+
`failed to read highstate manifest for package "%s"`,
|
150
|
+
packageName,
|
151
|
+
)
|
152
|
+
}
|
153
|
+
|
154
|
+
const sourceHash = manifest?.sourceHashes?.[relativePath]
|
155
|
+
|
156
|
+
if (sourceHash) {
|
157
|
+
this.logger.debug(`resolved source hash for package "%s"`, packageName)
|
158
|
+
return sourceHash
|
159
|
+
}
|
160
|
+
|
161
|
+
// use the package version as a fallback hash
|
162
|
+
// this case will be applied for most npm packages
|
163
|
+
this.logger.debug(`using package version as a fallback hash for "%s"`, packageName)
|
164
|
+
return depPackageJson.version ?? "0.0.0"
|
165
|
+
}
|
166
|
+
}
|
167
|
+
}
|
168
|
+
|
169
|
+
private async getPackageJson(basePath: string): Promise<[string, PackageJson]> {
|
170
|
+
while (true) {
|
171
|
+
const packageJson = await readPackageJSON(basePath)
|
172
|
+
if (packageJson.name) {
|
173
|
+
const packageJsonPath = await resolvePackageJSON(basePath)
|
174
|
+
|
175
|
+
return [packageJsonPath, packageJson]
|
176
|
+
}
|
177
|
+
|
178
|
+
basePath = resolve(dirname(basePath), "..")
|
179
|
+
}
|
180
|
+
}
|
181
|
+
|
182
|
+
private parseDependencies(filePath: string, content: string): FileDependency[] {
|
183
|
+
type DependencyMatch = {
|
184
|
+
relativePath?: string
|
185
|
+
nodeBuiltin?: string
|
186
|
+
npmPackage?: string
|
187
|
+
}
|
188
|
+
|
189
|
+
const dependencyRegex =
|
190
|
+
/^[ \t]*import[\s\S]*?\bfrom\s*["']((?<relativePath>\.\.?\/[^"']+)|(?<nodeBuiltin>node:[^"']+)|(?<npmPackage>[^"']+))["']/gm
|
191
|
+
|
192
|
+
const matches = content.matchAll(dependencyRegex)
|
193
|
+
const dependencies: FileDependency[] = []
|
194
|
+
|
195
|
+
for (const match of matches) {
|
196
|
+
const { nodeBuiltin, npmPackage, relativePath } = match.groups as DependencyMatch
|
197
|
+
|
198
|
+
if (relativePath) {
|
199
|
+
const fullPath = resolve(dirname(filePath), relativePath)
|
200
|
+
|
201
|
+
dependencies.push({
|
202
|
+
type: "relative",
|
203
|
+
id: `relative:${fullPath}`,
|
204
|
+
fullPath,
|
205
|
+
})
|
206
|
+
} else if (npmPackage) {
|
207
|
+
dependencies.push({
|
208
|
+
type: "npm",
|
209
|
+
id: `npm:${npmPackage}`,
|
210
|
+
package: npmPackage,
|
211
|
+
})
|
212
|
+
} else if (nodeBuiltin) {
|
213
|
+
// ignore node built-in modules
|
214
|
+
}
|
215
|
+
}
|
216
|
+
|
217
|
+
return dependencies
|
218
|
+
}
|
219
|
+
}
|