|
@@ -1,338 +0,0 @@
|
1
|
|
-#! /usr/bin/env node
|
2
|
|
-
|
3
|
|
-import * as program from 'commander'
|
4
|
|
-import { existsSync, readFileSync, writeFileSync } from 'fs'
|
5
|
|
-import { sync } from 'globby'
|
6
|
|
-import { dirname, relative, resolve } from 'path'
|
7
|
|
-import path from 'path'
|
8
|
|
-import fs from 'fs'
|
9
|
|
-import JSON5 from 'json5'
|
10
|
|
-
|
11
|
|
-/*
|
12
|
|
-"baseUrl": ".",
|
13
|
|
-"outDir": "lib",
|
14
|
|
-"paths": {
|
15
|
|
- "src/*": ["src/*"]
|
16
|
|
-},
|
17
|
|
-*/
|
18
|
|
-
|
19
|
|
-export interface IRawTSConfig {
|
20
|
|
- extends?: string
|
21
|
|
- compilerOptions?: {
|
22
|
|
- baseUrl?: string
|
23
|
|
- outDir?: string
|
24
|
|
- rootDir?: string
|
25
|
|
- paths?: { [key: string]: string[] }
|
26
|
|
- }
|
27
|
|
-}
|
28
|
|
-
|
29
|
|
-export interface ITSConfig {
|
30
|
|
- baseUrl?: string
|
31
|
|
- outDir?: string
|
32
|
|
- rootDir?: string
|
33
|
|
- compilerOptions?: Record<string, unknown>
|
34
|
|
- paths?: { [key: string]: string[] }
|
35
|
|
-}
|
36
|
|
-
|
37
|
|
-export const mapPaths = (
|
38
|
|
- paths: { [key: string]: string[] },
|
39
|
|
- mapper: (x: string) => string
|
40
|
|
-): { [key: string]: string[] } => {
|
41
|
|
- const dest = {} as { [key: string]: string[] }
|
42
|
|
- Object.keys(paths).forEach((key) => {
|
43
|
|
- dest[key] = paths[key].map(mapper)
|
44
|
|
- })
|
45
|
|
- return dest
|
46
|
|
-}
|
47
|
|
-
|
48
|
|
-export const loadConfig = (file: string): ITSConfig => {
|
49
|
|
- const fileToParse = fs.readFileSync(file)
|
50
|
|
-
|
51
|
|
- const parsedJsonFile = JSON5.parse(fileToParse as unknown as string)
|
52
|
|
-
|
53
|
|
- const {
|
54
|
|
- extends: extendsPath,
|
55
|
|
- compilerOptions: { baseUrl, outDir, rootDir, paths } = {
|
56
|
|
- baseUrl: undefined,
|
57
|
|
- outDir: undefined,
|
58
|
|
- rootDir: undefined,
|
59
|
|
- paths: undefined,
|
60
|
|
- },
|
61
|
|
- } = parsedJsonFile as IRawTSConfig
|
62
|
|
-
|
63
|
|
- const config: ITSConfig = {}
|
64
|
|
- if (baseUrl) {
|
65
|
|
- config.baseUrl = baseUrl
|
66
|
|
- }
|
67
|
|
- if (outDir) {
|
68
|
|
- config.outDir = outDir
|
69
|
|
- }
|
70
|
|
- if (rootDir) {
|
71
|
|
- config.rootDir = rootDir
|
72
|
|
- }
|
73
|
|
- if (paths) {
|
74
|
|
- config.paths = paths
|
75
|
|
- }
|
76
|
|
- if (extendsPath) {
|
77
|
|
- const childConfigDirPath = path.dirname(file)
|
78
|
|
- const parentConfigPath = path.resolve(childConfigDirPath, extendsPath)
|
79
|
|
- const parentConfigDirPath = path.dirname(parentConfigPath)
|
80
|
|
- const currentExtension = path.extname(parentConfigPath)
|
81
|
|
-
|
82
|
|
- let parentExtendedConfigFile = path.format({
|
83
|
|
- name: parentConfigPath,
|
84
|
|
- ext: currentExtension === '' ? '.json' : '',
|
85
|
|
- })
|
86
|
|
-
|
87
|
|
- /* Ensure without a doubt there's no double extension */
|
88
|
|
- if (/\.json\.json$/.test(parentExtendedConfigFile)) {
|
89
|
|
- parentExtendedConfigFile = parentExtendedConfigFile.replace(/\.json\.json$/, '.json')
|
90
|
|
- }
|
91
|
|
-
|
92
|
|
- const parentConfig = loadConfig(parentExtendedConfigFile)
|
93
|
|
-
|
94
|
|
- if (parentConfig.baseUrl) {
|
95
|
|
- parentConfig.baseUrl = path.resolve(parentConfigDirPath, parentConfig.baseUrl)
|
96
|
|
- }
|
97
|
|
-
|
98
|
|
- return {
|
99
|
|
- ...parentConfig,
|
100
|
|
- ...config,
|
101
|
|
- }
|
102
|
|
- }
|
103
|
|
-
|
104
|
|
- return config
|
105
|
|
-}
|
106
|
|
-
|
107
|
|
-program
|
108
|
|
- .version('0.0.1')
|
109
|
|
- .option('-p, --project <file>', 'path to tsconfig.json')
|
110
|
|
- .option('-s, --src <path>', 'source root path')
|
111
|
|
- .option('-o, --out <path>', 'output root path')
|
112
|
|
- .option('-v, --verbose', 'output logs')
|
113
|
|
-
|
114
|
|
-program.on('--help', () => {
|
115
|
|
- console.log(`
|
116
|
|
- $ tscpath -p tsconfig.json
|
117
|
|
-`)
|
118
|
|
-})
|
119
|
|
-
|
120
|
|
-program.parse(process.argv)
|
121
|
|
-
|
122
|
|
-const {
|
123
|
|
- out: flagOut,
|
124
|
|
- project = 'tsconfig.json',
|
125
|
|
- src: flagSrc,
|
126
|
|
- verbose = false,
|
127
|
|
-} = program as {
|
128
|
|
- out?: string | undefined
|
129
|
|
- project?: string
|
130
|
|
- src?: string | undefined
|
131
|
|
- verbose?: boolean
|
132
|
|
-}
|
133
|
|
-
|
134
|
|
-const verboseLog = (...args: any[]): void => {
|
135
|
|
- if (verbose) {
|
136
|
|
- console.log(...args)
|
137
|
|
- }
|
138
|
|
-}
|
139
|
|
-
|
140
|
|
-const configFile = resolve(process.cwd(), project)
|
141
|
|
-
|
142
|
|
-const rootDir = resolve(process.cwd())
|
143
|
|
-
|
144
|
|
-verboseLog(`Using tsconfig: ${configFile}`)
|
145
|
|
-
|
146
|
|
-const exitingErr = (): any => {
|
147
|
|
- throw new Error('--- exiting tsconfig-replace-paths due to parameters missing ---')
|
148
|
|
-}
|
149
|
|
-
|
150
|
|
-const missingConfigErr = (property: string): any => {
|
151
|
|
- console.error(`Whoops! Please set ${property} in your tsconfig or supply a flag`)
|
152
|
|
- exitingErr()
|
153
|
|
-}
|
154
|
|
-
|
155
|
|
-const missingDirectoryErr = (directory: string, flag: string): any => {
|
156
|
|
- console.error(
|
157
|
|
- `Whoops! ${directory} must be specified in your project => --project ${project}, or flagged with directory => ${flag} './path'`
|
158
|
|
- )
|
159
|
|
- exitingErr()
|
160
|
|
-}
|
161
|
|
-
|
162
|
|
-// Imported the TS Config
|
163
|
|
-const returnedTsConfig = loadConfig(configFile)
|
164
|
|
-
|
165
|
|
-// Destructure only the necessary keys, and rename to give context
|
166
|
|
-const {
|
167
|
|
- baseUrl,
|
168
|
|
- paths,
|
169
|
|
- outDir: tsConfigOutDir = '',
|
170
|
|
- rootDir: tsConfigRootDir = rootDir,
|
171
|
|
-} = returnedTsConfig
|
172
|
|
-
|
173
|
|
-// If no flagSrc or tsConfigRootDir, error
|
174
|
|
-if (!flagSrc && tsConfigRootDir === '') {
|
175
|
|
- missingConfigErr('compilerOptions.rootDir')
|
176
|
|
-}
|
177
|
|
-
|
178
|
|
-// If no flagOut or tsConfigOutDir, error
|
179
|
|
-if (!flagOut && tsConfigOutDir === '') {
|
180
|
|
- missingConfigErr('compilerOptions.outDir')
|
181
|
|
-}
|
182
|
|
-
|
183
|
|
-// Are we going to use the flag or ts config for src?
|
184
|
|
-let usingSrcDir: string
|
185
|
|
-if (flagSrc) {
|
186
|
|
- verboseLog('Using flag --src')
|
187
|
|
- usingSrcDir = resolve(flagSrc)
|
188
|
|
-} else {
|
189
|
|
- verboseLog('Using compilerOptions.rootDir from your tsconfig')
|
190
|
|
- usingSrcDir = resolve(tsConfigRootDir)
|
191
|
|
-}
|
192
|
|
-if (!usingSrcDir) {
|
193
|
|
- missingDirectoryErr('rootDir', '--src')
|
194
|
|
-}
|
195
|
|
-
|
196
|
|
-// Log which src is being used
|
197
|
|
-verboseLog(`Using src: ${usingSrcDir}`)
|
198
|
|
-
|
199
|
|
-// Are we going to use the flag or ts config for out?
|
200
|
|
-let usingOutDir: string
|
201
|
|
-if (flagOut) {
|
202
|
|
- verboseLog('Using flag --out')
|
203
|
|
- usingOutDir = resolve(flagOut)
|
204
|
|
-} else {
|
205
|
|
- verboseLog('Using compilerOptions.outDir from your tsconfig')
|
206
|
|
- usingOutDir = resolve(tsConfigOutDir)
|
207
|
|
-}
|
208
|
|
-if (!usingOutDir) {
|
209
|
|
- missingDirectoryErr('outDir', '--out')
|
210
|
|
-}
|
211
|
|
-
|
212
|
|
-// Log which out is being used
|
213
|
|
-verboseLog(`Using out: ${usingOutDir}`)
|
214
|
|
-
|
215
|
|
-if (!baseUrl) {
|
216
|
|
- throw new Error('compilerOptions.baseUrl is not set')
|
217
|
|
-}
|
218
|
|
-if (!paths) {
|
219
|
|
- throw new Error('compilerOptions.paths is not set')
|
220
|
|
-}
|
221
|
|
-if (!usingOutDir) {
|
222
|
|
- throw new Error('compilerOptions.outDir is not set')
|
223
|
|
-}
|
224
|
|
-if (!usingSrcDir) {
|
225
|
|
- throw new Error('compilerOptions.rootDir is not set')
|
226
|
|
-}
|
227
|
|
-
|
228
|
|
-verboseLog(`baseUrl: ${baseUrl}`)
|
229
|
|
-verboseLog(`rootDir: ${usingSrcDir}`)
|
230
|
|
-verboseLog(`outDir: ${usingOutDir}`)
|
231
|
|
-verboseLog(`paths: ${JSON.stringify(paths, null, 2)}`)
|
232
|
|
-
|
233
|
|
-const configDir = dirname(configFile)
|
234
|
|
-
|
235
|
|
-const basePath = resolve(configDir, baseUrl)
|
236
|
|
-verboseLog(`basePath: ${basePath}`)
|
237
|
|
-
|
238
|
|
-const outPath = usingOutDir || resolve(basePath, usingOutDir)
|
239
|
|
-verboseLog(`outPath: ${outPath}`)
|
240
|
|
-
|
241
|
|
-const outFileToSrcFile = (x: string): string => resolve(usingSrcDir, relative(outPath, x))
|
242
|
|
-
|
243
|
|
-const aliases = Object.keys(paths)
|
244
|
|
- .filter((path) => path.startsWith('~'))
|
245
|
|
- .map((alias) => ({
|
246
|
|
- prefix: alias.replace(/\*$/, ''),
|
247
|
|
- aliasPaths: paths[alias as keyof typeof paths].map((p) =>
|
248
|
|
- resolve(basePath, p.replace(/\*$/, ''))
|
249
|
|
- ),
|
250
|
|
- }))
|
251
|
|
- .filter(({ prefix }) => prefix)
|
252
|
|
-verboseLog(`aliases: ${JSON.stringify(aliases, null, 2)}`)
|
253
|
|
-
|
254
|
|
-const toRelative = (from: string, x: string): string => {
|
255
|
|
- const rel = relative(from, x)
|
256
|
|
- return (rel.startsWith('.') ? rel : `./${rel}`).replace(/\\/g, '/')
|
257
|
|
-}
|
258
|
|
-
|
259
|
|
-const exts = ['.js', '.jsx', '.ts', '.tsx', '.d.ts', '.json']
|
260
|
|
-
|
261
|
|
-let replaceCount = 0
|
262
|
|
-
|
263
|
|
-const absToRel = (modulePath: string, outFile: string): string => {
|
264
|
|
- const alen = aliases.length
|
265
|
|
-
|
266
|
|
- for (let j = 0; j < alen; j += 1) {
|
267
|
|
- const { prefix, aliasPaths } = aliases[j]
|
268
|
|
-
|
269
|
|
- if (modulePath.startsWith(prefix)) {
|
270
|
|
- const modulePathRel = modulePath.substring(prefix.length)
|
271
|
|
- const srcFile = outFileToSrcFile(outFile)
|
272
|
|
- const outRel = relative(basePath, outFile)
|
273
|
|
-
|
274
|
|
- verboseLog(`${outRel} (source: ${relative(basePath, srcFile)}):`)
|
275
|
|
- verboseLog(`\timport '${modulePath}'`)
|
276
|
|
-
|
277
|
|
- const len = aliasPaths.length
|
278
|
|
- for (let i = 0; i < len; i += 1) {
|
279
|
|
- const apath = aliasPaths[i]
|
280
|
|
- const moduleSrc = resolve(apath, modulePathRel)
|
281
|
|
- if (existsSync(moduleSrc) || exts.some((ext) => existsSync(moduleSrc + ext))) {
|
282
|
|
- const rel = toRelative(dirname(srcFile), moduleSrc)
|
283
|
|
-
|
284
|
|
- replaceCount += 1
|
285
|
|
-
|
286
|
|
- verboseLog(
|
287
|
|
- `\treplacing '${modulePath}' -> '${rel}' referencing ${relative(basePath, moduleSrc)}`
|
288
|
|
- )
|
289
|
|
- return rel
|
290
|
|
- }
|
291
|
|
- }
|
292
|
|
- verboseLog(`\tcould not replace ${modulePath}`)
|
293
|
|
- }
|
294
|
|
- }
|
295
|
|
-
|
296
|
|
- return modulePath
|
297
|
|
-}
|
298
|
|
-
|
299
|
|
-const requireRegex = /(?:import|require)\(['"]([^'"]*)['"]\)/g
|
300
|
|
-const importRegex = /(?:import|from) ['"]([^'"]*)['"]/g
|
301
|
|
-
|
302
|
|
-const replaceImportStatement = (orig: string, matched: string, outFile: string): string => {
|
303
|
|
- const index = orig.indexOf(matched)
|
304
|
|
- return (
|
305
|
|
- orig.substring(0, index) + absToRel(matched, outFile) + orig.substring(index + matched.length)
|
306
|
|
- )
|
307
|
|
-}
|
308
|
|
-
|
309
|
|
-const replaceAlias = (text: string, outFile: string): string =>
|
310
|
|
- text
|
311
|
|
- .replace(requireRegex, (orig, matched) => replaceImportStatement(orig, matched, outFile))
|
312
|
|
- .replace(importRegex, (orig, matched) => replaceImportStatement(orig, matched, outFile))
|
313
|
|
-
|
314
|
|
-// import relative to absolute path
|
315
|
|
-const files = sync(`${outPath}/**/*.{js,jsx,ts,tsx}`, {
|
316
|
|
- dot: true,
|
317
|
|
- noDir: true,
|
318
|
|
-} as any).map((x) => resolve(x))
|
319
|
|
-
|
320
|
|
-let changedFileCount = 0
|
321
|
|
-
|
322
|
|
-const flen = files.length
|
323
|
|
-let count = 0
|
324
|
|
-
|
325
|
|
-for (let i = 0; i < flen; i += 1) {
|
326
|
|
- const file = files[i]
|
327
|
|
- const text = readFileSync(file, 'utf8')
|
328
|
|
- const prevReplaceCount = replaceCount
|
329
|
|
- const newText = replaceAlias(text, file)
|
330
|
|
- if (text !== newText) {
|
331
|
|
- changedFileCount += 1
|
332
|
|
- verboseLog(`${file}: replaced ${replaceCount - prevReplaceCount} paths`)
|
333
|
|
- writeFileSync(file, newText, 'utf8')
|
334
|
|
- count = count + 1
|
335
|
|
- }
|
336
|
|
-}
|
337
|
|
-
|
338
|
|
-console.log(`Replaced ${replaceCount} paths in ${changedFileCount} files`)
|