-
Notifications
You must be signed in to change notification settings - Fork 1
/
npmi.ts
153 lines (141 loc) · 4.2 KB
/
npmi.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
import type {
DownloadArgs,
InstallArgs,
InstallConfigSimple,
ListAllArgs,
} from "../port.ts";
import {
$,
ALL_ARCH,
ALL_OS,
depExecShimPath,
osXarch,
pathsWithDepArts,
PortBase,
std_fs,
zod,
} from "../port.ts";
import node from "./node.ts";
import * as std_ports from "../modules/ports/std.ts";
const manifest = {
ty: "denoWorker@v1" as const,
name: "npmi_npm",
version: "0.1.0",
moduleSpecifier: import.meta.url,
buildDeps: [
std_ports.node_org,
],
// NOTE: enable all platforms. Restrictions will apply based
// node support this way
platforms: osXarch([...ALL_OS], [...ALL_ARCH]),
};
const confValidator = zod.object({
packageName: zod.string().regex(/[@/a-z0-9._-]*/),
}).passthrough();
export type NpmiInstallConf =
& InstallConfigSimple
& zod.input<typeof confValidator>;
const pkjJsonValidator = zod.object({
bin: zod.union([zod.string(), zod.record(zod.string(), zod.string())])
.nullish(),
// TODO: man pages
man: zod.union([zod.string(), zod.string().array()]).nullish(),
directories: zod.object({
bin: zod.string().nullish(),
man: zod.string().nullish(),
}).nullish(),
// TODO: find a way to utilize these
os: zod.string().nullish(),
cpu: zod.string().nullish(),
});
type PackageJson = zod.infer<typeof pkjJsonValidator>;
export default function conf(config: NpmiInstallConf) {
return [{
...config,
port: manifest,
}, node()];
}
export class Port extends PortBase {
async listAll(args: ListAllArgs) {
const conf = confValidator.parse(args.config);
const metadataRequest = await $.request(
`https://registry.npmjs.org/${conf.packageName}`,
).header(
{
// use abbreviated registry info which's still big
"Accept": "application/vnd.npm.install-v1+json",
},
);
const metadata = await metadataRequest.json() as {
versions: Record<string, PackageJson>;
};
const versions = Object.keys(metadata.versions);
return versions;
}
override async download(args: DownloadArgs) {
const conf = confValidator.parse(args.config);
await $.raw`${
depExecShimPath(std_ports.node_org, "npm", args.depArts)
} install --prefix ${args.tmpDirPath} --no-fund ${conf.packageName}@${args.installVersion}`
.cwd(args.tmpDirPath)
.env(pathsWithDepArts(args.depArts, args.platform.os));
await std_fs.move(args.tmpDirPath, args.downloadPath);
}
// FIXME: replace shebangs with the runtime dep node path
// default shebangs just use #!/bin/env node
override async install(args: InstallArgs) {
const conf = confValidator.parse(args.config);
await std_fs.copy(
args.downloadPath,
args.tmpDirPath,
{ overwrite: true },
);
const installPath = $.path(args.installPath);
const tmpDirPath = $.path(args.tmpDirPath);
const pkgDir = tmpDirPath.join("node_modules", conf.packageName);
const pkgJson = pkjJsonValidator.parse(
await pkgDir.join("package.json").readJson(),
);
const bins = [] as [string, string][];
if (pkgJson.bin) {
if (typeof pkgJson.bin == "string") {
const split = conf.packageName.split("/");
const pkgBaseName = split[split.length - 1];
bins.push([pkgBaseName, pkgJson.bin]);
} else {
bins.push(...Object.entries(pkgJson.bin));
}
} else if (pkgJson.directories?.bin) {
const pkgDirPathStrLen = pkgDir.toString().length;
bins.push(
...(await Array.fromAsync(
$.path(pkgDir.join(pkgJson.directories.bin)).walk({
includeDirs: false,
}),
))
.map((ent) =>
[
ent.name,
ent.path.toString().slice(pkgDirPathStrLen),
] as [string, string]
),
);
}
if (bins.length == 0) {
throw new Error("no artifacts to expose found", {
cause: conf,
});
}
await tmpDirPath.join("bin").ensureDir();
for (const [name] of bins) {
await tmpDirPath.join("bin", name)
.symlinkTo(
installPath
.join("node_modules", ".bin", name)
.toString(),
);
}
await $.removeIfExists(installPath);
await std_fs.move(tmpDirPath.toString(), installPath.toString());
}
}