-
Notifications
You must be signed in to change notification settings - Fork 34
/
index.ts
143 lines (115 loc) · 4.58 KB
/
index.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
import path from 'path';
import {existsSync} from 'fs';
// @ts-expect-error
import hasha from 'hasha';
import chokidar from 'chokidar';
import {importUserModule, logger, PackageInfo, ProjectAware, readPackageConfig} from '@reskript/core';
import {ProjectSettings, Listener, Observe, ClientProjectSettings, ReskriptProvider} from './interface.js';
import validate from './validate.js';
import {fillProjectSettings, PartialProjectSettings} from './defaults.js';
import {applyPlugins} from './plugins.js';
export * from './interface.js';
export {fillProjectSettings, PartialProjectSettings};
export interface UserSettings extends Omit<PartialProjectSettings, 'provider'> {
plugins?: ClientProjectSettings['plugins'];
}
export interface UserProjectSettings extends UserSettings {
provider: ReskriptProvider;
}
const requireSettings = async (cmd: ProjectAware, commandName: string): Promise<ProjectSettings> => {
const imported = await importUserModule<UserProjectSettings | {default: UserProjectSettings}>(
path.join(cmd.cwd, 'reskript.config'),
{default: {provider: 'webpack'}}
);
const userSettings = 'default' in imported ? imported.default : imported;
try {
validate(userSettings);
}
catch (ex) {
logger.error(ex instanceof Error ? ex.message : `${ex}`);
process.exit(21);
}
const {plugins = [], ...clientSettings} = userSettings;
const rawSettings = fillProjectSettings(clientSettings);
const pluginOptions = {...cmd, command: commandName};
return applyPlugins(rawSettings, plugins, pluginOptions);
};
const computeSettingsHash = async (cwd: string): Promise<string> => {
const location = path.join(cwd, 'reskript.config.js');
if (!existsSync(location)) {
return '';
}
return hasha.fromFile(location);
};
interface CacheContainer {
initialized: boolean;
hash: string;
settings: ProjectSettings;
listen: Observe | null;
}
const cache: CacheContainer = {
initialized: false,
hash: '',
settings: fillProjectSettings({provider: 'webpack'}),
listen: null,
};
export const readProjectSettings = async (cmd: ProjectAware, commandName: string): Promise<ProjectSettings> => {
if (cache.initialized) {
return cache.settings;
}
const settings = await requireSettings(cmd, commandName);
cache.initialized = true;
cache.settings = settings;
return settings;
};
export const watchProjectSettings = async (cmd: ProjectAware, commandName: string): Promise<Observe> => {
if (cache.listen) {
return cache.listen;
}
const settingsLocation = path.join(cmd.cwd, 'reskript.config.js');
const listeners = new Set<Listener>();
const watcher = chokidar.watch(settingsLocation);
const notify = async (): Promise<void> => {
// `fs.watch`是不稳定的,一次修改会触发多次,因此用hash做一下比对
const newSettingsHash = await computeSettingsHash(cmd.cwd);
if (newSettingsHash === cache.hash) {
return;
}
const newSettings = await requireSettings(cmd, commandName);
cache.hash = newSettingsHash;
cache.settings = newSettings;
listeners.forEach(f => f(newSettings));
};
// eslint-disable-next-line @typescript-eslint/no-misused-promises
watcher.on('all', notify);
cache.hash = await computeSettingsHash(cmd.cwd);
cache.listen = (listener: Listener) => {
listeners.add(listener);
return () => listeners.delete(listener);
};
return cache.listen;
};
export const warnAndExitOnInvalidFinalizeReturn = (value: any, scope: string): void => {
if (!value) {
const message = `
Your ${scope}.finalize returns nothing.
You may forget to write a return statement in ${scope}.finalize, or some plugin has a broken implement.
`;
logger.error(message);
process.exit(21);
}
};
const hasDependency = (packageInfo: PackageInfo, name: string) => {
const {dependencies, devDependencies} = packageInfo;
return !!(dependencies?.[name] || devDependencies?.[name]);
};
export const strictCheckRequiredDependency = async (projectSettings: ProjectSettings, cwd: string) => {
const hostPackageInfo = await readPackageConfig(cwd);
if (projectSettings.build.script.polyfill && !hasDependency(hostPackageInfo, 'core-js')) {
logger.error('You require polyfill on build but don\'t have core-js installed.');
process.exit(13);
}
};
export const configure = (provider: 'webpack', settings: UserSettings): UserProjectSettings => {
return {...settings, provider};
};