-
Notifications
You must be signed in to change notification settings - Fork 1
/
denoc.js
executable file
·121 lines (106 loc) · 3.68 KB
/
denoc.js
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
#!/usr/bin/env node
const fs = require('fs').promises;
const path = require('path');
const IMPORT_RE = /(?<=^|\n)((?:im|ex)port\s[\w\s{}*,\n]*['"])(.*)(['"];\n)/g;
const [,, root = process.cwd()] = process.argv;
function getConfig() {
const config = require(path.join(root, 'package.json')).denoc;
if (!config) {
throw new Error(`No 'denoc' config found`);
}
return config;
}
function mapDependency(dep, prefix, suffix, onImport, parentFile) {
if (!dep) {
return '';
}
if (typeof dep === 'string') {
if (dep.startsWith('.')) {
onImport(false, path.normalize(dep));
return `${prefix}./${path.relative(path.dirname(parentFile), dep)}${suffix}`;
}
return `${prefix}${dep}${suffix}`;
}
if (!dep[parentFile] || typeof dep[parentFile] !== 'string') {
throw new Error(`Missing '${parentFile}' in '${dep}'`);
}
return mapDependency(dep[parentFile], prefix, suffix, onImport);
}
function resolveImport(parentFile, id) {
return id.startsWith('.') ? path.join(path.dirname(parentFile), `${id}.ts`) : null;
}
function mapImport(parentFile, prefix, id, suffix, dependencies, onImport) {
const file = resolveImport(parentFile, id);
if (file) {
if (file in dependencies) {
return mapDependency(dependencies[file], prefix, suffix, onImport, parentFile)
}
onImport(true, file);
return `${prefix}${id}.ts${suffix}`;
} else if (id in dependencies) {
return mapDependency(dependencies[id], prefix, suffix, onImport, parentFile);
}
throw new Error(`Unhandled dependency '${id}' in '${parentFile}'.`);
}
function extractImports(parentFile, matches, onImport) {
for (const [_1, _2, id] of matches) {
const file = resolveImport(parentFile, id);
if (file) {
onImport(false, file);
}
}
}
async function denocFile(translate, srcRoot, file, outDir, dependencies) {
const imports = [];
const onImport = (translate, file) => imports.push({ translate, file });
const srcFile = path.join(root, file);
const dstFile = path.join(root, outDir, path.relative(srcRoot, file));
await fs.mkdir(path.dirname(dstFile), { recursive: true });
const src = await fs.readFile(srcFile, 'utf-8');
if (translate) {
const dst = src.replace(IMPORT_RE, (_, prefix, id, suffix) =>
mapImport(file, prefix, id, suffix, dependencies, onImport)
);
await fs.writeFile(dstFile, dst);
} else {
extractImports(file, src.matchAll(IMPORT_RE), onImport);
await fs.copyFile(srcFile, dstFile);
}
for (const { translate, file } of imports) {
await denocFile(translate, srcRoot, file, outDir, dependencies);
}
}
function makeRelativeRoot(p, def) {
return p ? (path.isAbsolute(p) ? path.relative(root, p) : p) : def;
}
async function copyFiles(copy, outDir) {
await Promise.all(copy.map(file => makeRelativeRoot(file)).map(async file => {
const srcFile = path.join(root, file);
const dstFile = path.join(root, outDir, path.relative(root, file));
await fs.mkdir(path.dirname(dstFile), { recursive: true });
await fs.copyFile(srcFile, dstFile);
}));
}
async function renderFiles(main, outDir, dependencies, copy) {
const srcRoot = path.dirname(main);
await Promise.all([
denocFile(true, srcRoot, main, outDir, dependencies),
copyFiles(copy, outDir),
]);
}
async function main() {
const config = getConfig();
if (!config.main) {
console.error(`'main' not specified array.`);
process.exit(1);
}
const main = makeRelativeRoot(config.main);
const outDir = makeRelativeRoot(config.outDir, '.');
const dependencies = config.dependencies ?? {};
const copy = config.copy ?? [];
await renderFiles(main, outDir, dependencies, copy);
}
main().catch(err => {
console.error(err);
process.exit(1);
});