@cap-js/cds-typer 0.33.0 → 0.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cds-plugin.js +15 -3
- package/lib/cli.js +8 -3
- package/lib/compile.js +47 -4
- package/lib/components/enum.js +5 -1
- package/lib/file.js +1 -1
- package/lib/printers/javascript.js +12 -5
- package/lib/typedefs.d.ts +1 -0
- package/package.json +16 -9
package/cds-plugin.js
CHANGED
|
@@ -5,9 +5,12 @@ const util = require('util')
|
|
|
5
5
|
const exec = util.promisify(require('child_process').exec)
|
|
6
6
|
const typer = require('./lib/compile')
|
|
7
7
|
const { fs, path } = cds.utils
|
|
8
|
+
const { configuration } = require('./lib/config')
|
|
9
|
+
|
|
8
10
|
const DEBUG = cds.debug('cli|build')
|
|
9
11
|
const BUILD_CONFIG = 'tsconfig.cdsbuild.json'
|
|
10
|
-
const
|
|
12
|
+
const DEFAULT_MODEL_DIRECTORY_NAME = '@cds-models'
|
|
13
|
+
|
|
11
14
|
|
|
12
15
|
/**
|
|
13
16
|
* Check if the project is a TypeScript project by looking for a dependency on TypeScript.
|
|
@@ -54,6 +57,15 @@ const rmFiles = async (dir, exts) => fs.existsSync(dir)
|
|
|
54
57
|
|
|
55
58
|
// IIFE to be able to return early
|
|
56
59
|
;(() => {
|
|
60
|
+
if (cds.watched) {
|
|
61
|
+
if (fs.existsSync(cds.env.typer.output_directory)) return
|
|
62
|
+
DEBUG?.('>> start cds-typer before cds watch')
|
|
63
|
+
module.exports = typer.compileFromFile('*')
|
|
64
|
+
.then(() => DEBUG?.('<< end cds-typer before cds watch'))
|
|
65
|
+
.catch(e => DEBUG?.(e))
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
|
|
57
69
|
// FIXME: remove once cds7 has been phased out
|
|
58
70
|
if (!cds?.version || cds.version < '8.0.0') {
|
|
59
71
|
DEBUG?.('typescript build task requires @sap/cds-dk version >= 8.0.0, skipping registration')
|
|
@@ -94,7 +106,7 @@ const rmFiles = async (dir, exts) => fs.existsSync(dir)
|
|
|
94
106
|
} catch {
|
|
95
107
|
DEBUG?.('tsconfig.json not found, not parsable, or inconclusive. Using default model directory name')
|
|
96
108
|
}
|
|
97
|
-
return
|
|
109
|
+
return DEFAULT_MODEL_DIRECTORY_NAME
|
|
98
110
|
}
|
|
99
111
|
|
|
100
112
|
init() {
|
|
@@ -151,4 +163,4 @@ const rmFiles = async (dir, exts) => fs.existsSync(dir)
|
|
|
151
163
|
this.#copyCleanModel(buildDirCdsModels)
|
|
152
164
|
}
|
|
153
165
|
})
|
|
154
|
-
})()
|
|
166
|
+
})()
|
package/lib/cli.js
CHANGED
|
@@ -174,7 +174,7 @@ const flags = enrichFlagSchema({
|
|
|
174
174
|
},
|
|
175
175
|
useEntitiesProxy: parameterTypes.boolean({
|
|
176
176
|
desc: `If set to true the 'cds.entities' exports in the generated 'index.js'${EOL}files will be wrapped in 'Proxy' objects\nso static import/require calls can be used everywhere.${EOL}${EOL}WARNING: entity properties can still only be accessed after${EOL}'cds.entities' has been loaded`,
|
|
177
|
-
default: '
|
|
177
|
+
default: 'true'
|
|
178
178
|
}),
|
|
179
179
|
version: {
|
|
180
180
|
desc: 'Prints the version of this tool.'
|
|
@@ -185,7 +185,7 @@ const flags = enrichFlagSchema({
|
|
|
185
185
|
default: 'flat'
|
|
186
186
|
},
|
|
187
187
|
propertiesOptional: parameterTypes.boolean({
|
|
188
|
-
desc: `If set to true, properties in entities are${EOL}always generated as optional (a?: T).`,
|
|
188
|
+
desc: `If set to true, properties in entities are${EOL}always generated as optional (a?: T).${EOL}Setting it to false makes properties non-optional instead (a: T).`,
|
|
189
189
|
default: 'true'
|
|
190
190
|
}),
|
|
191
191
|
IEEE754Compatible: parameterTypes.boolean({
|
|
@@ -204,7 +204,12 @@ const flags = enrichFlagSchema({
|
|
|
204
204
|
buildTask: parameterTypes.boolean({
|
|
205
205
|
desc: `If set to true, the typescript build task will not be registered/ executed.${EOL}This value must be set in your project configuration.${EOL}Passing it as parameter to the cds-typer CLI has no effect.`,
|
|
206
206
|
default: 'true'
|
|
207
|
-
})
|
|
207
|
+
}),
|
|
208
|
+
cache: {
|
|
209
|
+
desc: `How to cache typer runs.${EOL}none: fully run cds-typer whenever it is called${EOL}blake2s256: only run if the blake2s256-hash of the model has changed. Hash is stored in a file between runs.`,
|
|
210
|
+
allowed: ['none', 'blake2s256'],
|
|
211
|
+
default: 'none'
|
|
212
|
+
}
|
|
208
213
|
})
|
|
209
214
|
|
|
210
215
|
const hint = () => 'Missing or invalid parameter(s). Call with --help for more details.'
|
package/lib/compile.js
CHANGED
|
@@ -1,13 +1,35 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const fs = require('fs')
|
|
4
|
-
const { normalize } = require('path')
|
|
4
|
+
const { normalize, join } = require('path')
|
|
5
5
|
const cds = require(require.resolve('@sap/cds', { paths: [process.cwd(), __dirname] }))
|
|
6
6
|
const util = require('./util')
|
|
7
7
|
const { writeout } = require('./file')
|
|
8
8
|
const { Visitor } = require('./visitor')
|
|
9
9
|
const { LOG, setLevel } = require('./logging')
|
|
10
10
|
const { configuration } = require('./config')
|
|
11
|
+
const cdsTyperVersion = process.env.npm_package_version
|
|
12
|
+
?? require('../package.json').version
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {JSON} csn - the model
|
|
16
|
+
* @param {string} hashFile - path to the hash file
|
|
17
|
+
* @returns {[boolean, string]}
|
|
18
|
+
*/
|
|
19
|
+
const checkHash = (csn, hashFile) => {
|
|
20
|
+
const crypto = require('crypto')
|
|
21
|
+
const currentHash = crypto
|
|
22
|
+
.createHash('blake2s256')
|
|
23
|
+
.update(JSON.stringify({'cds-typer-version': cdsTyperVersion, csn}))
|
|
24
|
+
.digest('hex')
|
|
25
|
+
if (fs.existsSync(hashFile)) {
|
|
26
|
+
const oldHash = fs.readFileSync(hashFile, 'utf8')
|
|
27
|
+
if (oldHash === currentHash) {
|
|
28
|
+
return [false, currentHash]
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return [true, currentHash]
|
|
32
|
+
}
|
|
11
33
|
|
|
12
34
|
/**
|
|
13
35
|
* Writes the accompanying jsconfig.json file to the specified paths.
|
|
@@ -39,11 +61,11 @@ const writeJsConfig = path => {
|
|
|
39
61
|
* @param {{xtended: import('./typedefs').resolver.CSN, inferred: import('./typedefs').resolver.CSN}} csn - csn tuple
|
|
40
62
|
*/
|
|
41
63
|
const compileFromCSN = async csn => {
|
|
42
|
-
|
|
43
64
|
setLevel(configuration.logLevel)
|
|
44
65
|
if (configuration.jsConfigPath) {
|
|
45
66
|
writeJsConfig(configuration.jsConfigPath)
|
|
46
67
|
}
|
|
68
|
+
|
|
47
69
|
return writeout(
|
|
48
70
|
configuration.outputDirectory,
|
|
49
71
|
Object.values(new Visitor(csn).getWriteoutFiles())
|
|
@@ -55,10 +77,31 @@ const compileFromCSN = async csn => {
|
|
|
55
77
|
* @param {string | string[]} inputFile - path to input .cds file
|
|
56
78
|
*/
|
|
57
79
|
const compileFromFile = async inputFile => {
|
|
80
|
+
const hashFile = join(configuration.outputDirectory, '.hash')
|
|
58
81
|
const paths = typeof inputFile === 'string' ? normalize(inputFile) : inputFile.map(f => normalize(f))
|
|
59
|
-
const
|
|
82
|
+
const xtendedPlain = await cds.load(paths, { docs: true, flavor: 'xtended' })
|
|
83
|
+
let modelHash
|
|
84
|
+
|
|
85
|
+
if (configuration.cache === 'blake2s256') {
|
|
86
|
+
const [shouldCompile, hash] = checkHash(xtendedPlain, hashFile)
|
|
87
|
+
if (!shouldCompile) {
|
|
88
|
+
LOG.info('Model has not changed. Skipping generation.')
|
|
89
|
+
return
|
|
90
|
+
}
|
|
91
|
+
modelHash = hash
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const xtended = await cds.linked(xtendedPlain)
|
|
60
95
|
const inferred = await cds.linked(await cds.load(paths, { docs: true }))
|
|
61
|
-
|
|
96
|
+
const result = await compileFromCSN({xtended, inferred})
|
|
97
|
+
|
|
98
|
+
if (modelHash) {
|
|
99
|
+
// write hash as last thing before returning,
|
|
100
|
+
// so that we don't write it if an error occurs,
|
|
101
|
+
// which would block the next run although the model has changed
|
|
102
|
+
fs.writeFileSync(hashFile, modelHash)
|
|
103
|
+
}
|
|
104
|
+
return result
|
|
62
105
|
}
|
|
63
106
|
|
|
64
107
|
module.exports = {
|
package/lib/components/enum.js
CHANGED
|
@@ -122,9 +122,13 @@ const isInlineEnumType = (element, csn) => element.enum
|
|
|
122
122
|
* ```
|
|
123
123
|
* @param {string} name - the enum name
|
|
124
124
|
* @param {[string, string][]} kvs - a list of key-value pairs. Values that are falsey are replaced by
|
|
125
|
+
* @param {import('../printers/javascript').Printer} jsp - the printer to use
|
|
125
126
|
*/
|
|
126
127
|
// ??= for inline enums. If there is some static property of that name, we don't want to override it (for example: ".actions"
|
|
127
|
-
const stringifyEnumImplementation = (name, kvs
|
|
128
|
+
const stringifyEnumImplementation = (name, kvs, jsp) => jsp.printExport(
|
|
129
|
+
name,
|
|
130
|
+
`{ ${kvs.map(([k,v]) => `${normalise(k)}: ${v}`).join(', ')} }`,
|
|
131
|
+
{ coalesce: true })
|
|
128
132
|
|
|
129
133
|
|
|
130
134
|
module.exports = {
|
package/lib/file.js
CHANGED
|
@@ -577,7 +577,7 @@ class SourceFile extends File {
|
|
|
577
577
|
.concat(jsp.printSingleLineComment('actions'))
|
|
578
578
|
.concat(this.operations.names.map(name => jsp.printExport(name, stringIdent(name))))
|
|
579
579
|
.concat(jsp.printSingleLineComment('enums'))
|
|
580
|
-
.concat(this.enums.data.map(({name, kvs}) => stringifyEnumImplementation(name, kvs)))
|
|
580
|
+
.concat(this.enums.data.map(({name, kvs}) => stringifyEnumImplementation(name, kvs, jsp)))
|
|
581
581
|
.join('\n') + '\n'
|
|
582
582
|
}
|
|
583
583
|
}
|
|
@@ -48,10 +48,11 @@ class JavaScriptPrinter {
|
|
|
48
48
|
* @abstract
|
|
49
49
|
* @param {string} name - name the export should be known as
|
|
50
50
|
* @param {string} value - the value of the export
|
|
51
|
+
* @param {{coalesce?: boolean}} [options] - additional options
|
|
51
52
|
* @returns {string}
|
|
52
53
|
*/
|
|
53
54
|
// eslint-disable-next-line no-unused-vars
|
|
54
|
-
printExport (name, value) {
|
|
55
|
+
printExport (name, value, options = {}) {
|
|
55
56
|
throw Error('not implemented')
|
|
56
57
|
}
|
|
57
58
|
|
|
@@ -88,9 +89,12 @@ class ESMPrinter extends JavaScriptPrinter {
|
|
|
88
89
|
}
|
|
89
90
|
|
|
90
91
|
/** @type {JavaScriptPrinter['printExport']} */
|
|
91
|
-
printExport (name, value) {
|
|
92
|
+
printExport (name, value, options) {
|
|
93
|
+
const op = options?.coalesce
|
|
94
|
+
? '??='
|
|
95
|
+
: '='
|
|
92
96
|
return name.includes('.')
|
|
93
|
-
? `${name}
|
|
97
|
+
? `${name} ${op} ${value}`
|
|
94
98
|
: `export const ${name} = ${value}`
|
|
95
99
|
}
|
|
96
100
|
|
|
@@ -117,8 +121,11 @@ class CJSPrinter extends JavaScriptPrinter {
|
|
|
117
121
|
}
|
|
118
122
|
|
|
119
123
|
/** @type {JavaScriptPrinter['printExport']} */
|
|
120
|
-
printExport (name, value) {
|
|
121
|
-
|
|
124
|
+
printExport (name, value, options) {
|
|
125
|
+
const op = options?.coalesce
|
|
126
|
+
? '??='
|
|
127
|
+
: '='
|
|
128
|
+
return `module.exports.${name} ${op} ${value}`
|
|
122
129
|
}
|
|
123
130
|
|
|
124
131
|
/** @type {JavaScriptPrinter['printDefaultExport']} */
|
package/lib/typedefs.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cap-js/cds-typer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.34.0",
|
|
4
4
|
"description": "Generates .ts files for a CDS model to receive code completion in VS Code",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"repository": "github:cap-js/cds-typer",
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
"test:unit": "node test/testRunner.js ./test/unit ./test/unit/setup.mjs",
|
|
17
17
|
"test:integration": "node test/testRunner.js ./test/integration ./test/integration/setup.mjs",
|
|
18
18
|
"test:smoke": "node test/testRunner.js ./test/smoke ./test/smoke/setup.mjs",
|
|
19
|
-
"test:all": "npm run test:smoke && npm run test:unit",
|
|
20
|
-
"test": "npm run test:
|
|
19
|
+
"test:all": "npm run test:smoke && npm run test:unit && npm run test:integration",
|
|
20
|
+
"test": "npm run test:all",
|
|
21
21
|
"lint": "npx eslint .",
|
|
22
22
|
"lint:fix": "npx eslint . --fix",
|
|
23
23
|
"cli": "node lib/cli.js",
|
|
@@ -45,12 +45,10 @@
|
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@cap-js/cds-types": "^0",
|
|
48
|
-
"@
|
|
49
|
-
"@stylistic/eslint-plugin-js": "^3.0.0",
|
|
48
|
+
"@stylistic/eslint-plugin-js": "^4.2.0",
|
|
50
49
|
"acorn": "^8.10.0",
|
|
51
50
|
"eslint": "^9",
|
|
52
51
|
"eslint-plugin-jsdoc": "^50.2.2",
|
|
53
|
-
"globals": "^15.0.0",
|
|
54
52
|
"typescript": ">=4.6.4"
|
|
55
53
|
},
|
|
56
54
|
"cds": {
|
|
@@ -59,7 +57,7 @@
|
|
|
59
57
|
"inline_declarations": "flat",
|
|
60
58
|
"target_module_type": "auto",
|
|
61
59
|
"properties_optional": true,
|
|
62
|
-
"use_entities_proxy":
|
|
60
|
+
"use_entities_proxy": true,
|
|
63
61
|
"build_task": true
|
|
64
62
|
},
|
|
65
63
|
"schema": {
|
|
@@ -72,6 +70,15 @@
|
|
|
72
70
|
"type": "object",
|
|
73
71
|
"description": "Configuration for CDS Typer",
|
|
74
72
|
"properties": {
|
|
73
|
+
"cache": {
|
|
74
|
+
"type": "string",
|
|
75
|
+
"description": "How to cache typer runs.\nnone: fully run cds-typer whenever it is called\nblake2s256: only run if the blake2s256-hash of the model has changed. Hash is stored in a file between runs.",
|
|
76
|
+
"enum": [
|
|
77
|
+
"none",
|
|
78
|
+
"blake2s256"
|
|
79
|
+
],
|
|
80
|
+
"default": "none"
|
|
81
|
+
},
|
|
75
82
|
"output_directory": {
|
|
76
83
|
"type": "string",
|
|
77
84
|
"description": "Root directory to write the generated files to.",
|
|
@@ -102,7 +109,7 @@
|
|
|
102
109
|
"use_entities_proxy": {
|
|
103
110
|
"type": "boolean",
|
|
104
111
|
"description": "If set to true the 'cds.entities' exports in the generated 'index.js'\nfiles will be wrapped in 'Proxy' objects\nso static import/require calls can be used everywhere.\n\nWARNING: entity properties can still only be accessed after\n'cds.entities' has been loaded",
|
|
105
|
-
"default":
|
|
112
|
+
"default": true
|
|
106
113
|
},
|
|
107
114
|
"inline_declarations": {
|
|
108
115
|
"type": "string",
|
|
@@ -115,7 +122,7 @@
|
|
|
115
122
|
},
|
|
116
123
|
"properties_optional": {
|
|
117
124
|
"type": "boolean",
|
|
118
|
-
"description": "If set to true, properties in entities are\nalways generated as optional (a?: T).",
|
|
125
|
+
"description": "If set to true, properties in entities are\nalways generated as optional (a?: T).\nSetting it to false makes properties non-optional instead (a: T).",
|
|
119
126
|
"default": true
|
|
120
127
|
},
|
|
121
128
|
"ieee754compatible": {
|