generated from SAP/repository-template
-
Notifications
You must be signed in to change notification settings - Fork 10
/
cds-plugin.js
144 lines (127 loc) · 5.26 KB
/
cds-plugin.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
const { readdir, stat } = require('node:fs/promises')
const { normalize } = require('node:path')
const cds = require('@sap/cds')
const util = require('util')
const exec = util.promisify(require('child_process').exec)
const typer = require('./lib/compile')
const { fs, path } = cds.utils
const DEBUG = cds.debug('cli|build')
const BUILD_CONFIG = 'tsconfig.cdsbuild.json'
/**
* Check if the project is a TypeScript project by looking for a dependency on TypeScript.
* @returns {boolean}
*/
const isTypeScriptProject = () => {
if (!fs.existsSync('package.json')) return false
const pkg = require(path.resolve('package.json'))
return Boolean(pkg.devDependencies?.typescript || pkg.dependencies?.typescript)
}
/**
* Check if separate tsconfig file that is used for building the project.
* @returns {boolean}
*/
const buildConfigExists = () => fs.existsSync(BUILD_CONFIG)
/**
* @param {string} dir - The directory to remove.
*/
const rmDirIfExists = dir => {
try { fs.rmSync(dir, { recursive: true }) } catch { /* ignore */ }
}
/**
* Remove files with given extensions from a directory recursively.
* @param {string} dir - The directory to start from.
* @param {string[]} exts - The extensions to remove.
* @returns {Promise<unknown>}
*/
const rmFiles = async (dir, exts) => fs.existsSync(dir)
? Promise.all(
(await readdir(dir))
.map(async file => {
const filePath = path.join(dir, file)
if ((await stat(filePath)).isDirectory()) {
return rmFiles(filePath, exts)
} else if (exts.some(ext => file.endsWith(ext))) {
fs.unlinkSync(filePath)
}
})
)
: undefined
// FIXME: remove once cds7 has been phased out
if (!cds?.version || cds.version < '8.0.0') {
DEBUG?.('typescript build task requires @sap/cds-dk version >= 8.0.0, skipping registration')
return
}
// requires @sap/cds-dk version >= 7.5.0
cds.build?.register?.('typescript', class extends cds.build.Plugin {
static taskDefaults = { src: '.' }
static hasTask() { return isTypeScriptProject() }
// lower priority than the nodejs task
get priority() { return -1 }
get #appFolder () { return cds?.env?.folders?.app ?? 'app' }
/**
* cds.env > tsconfig.compilerOptions.paths > '@cds-models' (default)
*/
get #modelDirectoryName () {
const outputDirectory = cds.env.typer?.outputDirectory
if (outputDirectory) return outputDirectory
try {
// expected format: { '#cds-models/*': [ './@cds-models/*' ] }
// ^^^^^^^^^^^
// relevant part - may be changed by user
const config = JSON.parse(fs.readFileSync ('tsconfig.json', 'utf8'))
const alias = config.compilerOptions.paths['#cds-models/*'][0]
const directory = alias.match(/(?:\.\/)?(.*)\/\*/)[1]
return normalize(directory) // could contain forward slashes in tsconfig.json
} catch {
DEBUG?.('tsconfig.json not found, not parsable, or inconclusive. Using default model directory name')
}
return '@cds-models'
}
init() {
this.task.dest = path.join(cds.root, cds.env.build.target, cds.env.folders.srv)
}
async #runCdsTyper () {
DEBUG?.('running cds-typer')
cds.env.typer ??= {}
cds.env.typer.outputDirectory ??= this.#modelDirectoryName
await typer.compileFromFile('*')
}
async #buildWithConfig () {
// possibly referencing their tsconfig.json via "extends", specifying the "compilerOptions.outDir" and
// manually adding irrelevant folders (read: gen/ and app/) to the "exclude" array.
DEBUG?.(`building with config ${BUILD_CONFIG}`)
return exec(`npx tsc --project ${BUILD_CONFIG}`)
}
async #buildWithoutConfig () {
DEBUG?.('building without config')
// this will include gen/ that was created by the nodejs task
// _within_ the project directory. So we need to remove it afterwards.
await exec(`npx tsc --outDir "${this.task.dest.replace(/\\/g, '/')}"`) // see https://github.com/cap-js/cds-typer/issues/374
rmDirIfExists(path.join(this.task.dest, cds.env.build.target))
rmDirIfExists(path.join(this.task.dest, this.#appFolder))
}
async #copyCleanModel (buildDirCdsModels) {
// copy models again, to revert transpilation thereof.
// We only need the index.js files in un-transpiled form.
await this.copy(this.#modelDirectoryName).to(buildDirCdsModels)
await rmFiles(buildDirCdsModels, ['.ts'])
}
async build() {
await this.#runCdsTyper()
const buildDirCdsModels = path.join(this.task.dest, this.#modelDirectoryName)
// remove the js files generated by the nodejs buildtask,
// leaving only json, cds, and other static files
await rmFiles(this.task.dest, ['.js', '.ts'])
try {
await (buildConfigExists()
? this.#buildWithConfig()
: this.#buildWithoutConfig()
)
} catch (error) {
throw error.stdout
? new Error(error.stdout)
: error
}
this.#copyCleanModel(buildDirCdsModels)
}
})