/
utils.js
180 lines (162 loc) · 5.05 KB
/
utils.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
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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
import path from 'path';
import { loadConfigFromFile, loadEnv, mergeConfig } from 'vite';
import { runtime_directory } from '../../core/utils.js';
import { posixify } from '../../utils/filesystem.js';
import { negotiate } from '../../utils/http.js';
/**
* @param {import('vite').ResolvedConfig} config
* @param {import('vite').ConfigEnv} config_env
* @return {Promise<import('vite').UserConfig>}
*/
export async function get_vite_config(config, config_env) {
const loaded = await loadConfigFromFile(
config_env,
config.configFile,
undefined,
config.logLevel
);
if (!loaded) {
throw new Error('Could not load Vite config');
}
return mergeConfig(loaded.config, {
// CLI opts
mode: config_env.mode,
logLevel: config.logLevel,
clearScreen: config.clearScreen,
optimizeDeps: { force: config.optimizeDeps.force }
});
}
/**
* Takes zero or more objects and returns a new object that has all the values
* deeply merged together. None of the original objects will be mutated at any
* level, and the returned object will have no references to the original
* objects at any depth. If there's a conflict the last one wins, except for
* arrays which will be combined.
* @param {...Object} objects
* @returns {Record<string, any>} the merged object
*/
export function deep_merge(...objects) {
const result = {};
/** @type {string[]} */
objects.forEach((o) => merge_into(result, o));
return result;
}
/**
* Merges b into a, recursively, mutating a.
* @param {Record<string, any>} a
* @param {Record<string, any>} b
*/
function merge_into(a, b) {
/**
* Checks for "plain old Javascript object", typically made as an object
* literal. Excludes Arrays and built-in types like Buffer.
* @param {any} x
*/
const is_plain_object = (x) => typeof x === 'object' && x.constructor === Object;
for (const prop in b) {
if (is_plain_object(b[prop])) {
if (!is_plain_object(a[prop])) {
a[prop] = {};
}
merge_into(a[prop], b[prop]);
} else if (Array.isArray(b[prop])) {
if (!Array.isArray(a[prop])) {
a[prop] = [];
}
a[prop].push(...b[prop]);
} else {
a[prop] = b[prop];
}
}
}
/**
* Transforms kit.alias to a valid vite.resolve.alias array.
*
* Related to tsconfig path alias creation.
*
* @param {import('types').ValidatedKitConfig} config
* */
export function get_config_aliases(config) {
/** @type {import('vite').Alias[]} */
const alias = [
// For now, we handle `$lib` specially here rather than make it a default value for
// `config.kit.alias` since it has special meaning for packaging, etc.
{ find: '$lib', replacement: config.files.lib }
];
for (let [key, value] of Object.entries(config.alias)) {
value = posixify(value);
if (value.endsWith('/*')) {
value = value.slice(0, -2);
}
if (key.endsWith('/*')) {
// Doing just `{ find: key.slice(0, -2) ,..}` would mean `import .. from "key"` would also be matched, which we don't want
alias.push({
find: new RegExp(`^${escape_for_regexp(key.slice(0, -2))}\\/(.+)$`),
replacement: `${path.resolve(value)}/$1`
});
} else if (key + '/*' in config.alias) {
// key and key/* both exist -> the replacement for key needs to happen _only_ on import .. from "key"
alias.push({
find: new RegExp(`^${escape_for_regexp(key)}$`),
replacement: path.resolve(value)
});
} else {
alias.push({ find: key, replacement: path.resolve(value) });
}
}
return alias;
}
/**
* Returns Vite aliases for generated and runtime files.
*
* @param {import('types').ValidatedKitConfig} config
* */
export function get_app_aliases(config) {
/** @type {import('vite').Alias[]} */
const alias = [
{ find: '__GENERATED__', replacement: path.posix.join(config.outDir, 'generated') },
{ find: '$app', replacement: `${runtime_directory}/app` }
];
return alias;
}
/**
* @param {string} str
*/
function escape_for_regexp(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, (match) => '\\' + match);
}
/**
* Load environment variables from process.env and .env files
* @param {import('types').ValidatedKitConfig['env']} env_config
* @param {string} mode
*/
export function get_env(env_config, mode) {
const entries = Object.entries(loadEnv(mode, env_config.dir, ''));
return {
public: Object.fromEntries(entries.filter(([k]) => k.startsWith(env_config.publicPrefix))),
private: Object.fromEntries(entries.filter(([k]) => !k.startsWith(env_config.publicPrefix)))
};
}
/**
* @param {import('http').IncomingMessage} req
* @param {import('http').ServerResponse} res
* @param {string} base
*/
export function not_found(req, res, base) {
const type = negotiate(req.headers.accept ?? '*', ['text/plain', 'text/html']);
// special case — handle `/` request automatically
if (req.url === '/' && type === 'text/html') {
res.statusCode = 307;
res.setHeader('location', base);
res.end();
return;
}
res.statusCode = 404;
const prefixed = base + req.url;
if (type === 'text/html') {
res.setHeader('Content-Type', 'text/html');
res.end(`Not found (did you mean <a href="${prefixed}">${prefixed}</a>?)`);
} else {
res.end(`Not found (did you mean ${prefixed}?)`);
}
}