@cap-js/cds-typer 0.33.1 → 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 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 { configuration } = require('./lib/config')
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 '@cds-models'
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: 'false'
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 xtended = await cds.linked(await cds.load(paths, { docs: true, flavor: 'xtended' }))
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
- return compileFromCSN({xtended, inferred})
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/typedefs.d.ts CHANGED
@@ -188,6 +188,7 @@ export module config {
188
188
 
189
189
  export type Configuration = {
190
190
  outputDirectory: string,
191
+ cache: 'none' | 'blake2s256'
191
192
  logLevel: number,
192
193
  /**
193
194
  * `useEntitiesProxy = true` will wrap the `module.exports.<entityName>` in `Proxy` objects
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cap-js/cds-typer",
3
- "version": "0.33.1",
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:smoke && npm run test:unit",
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,7 +45,6 @@
45
45
  },
46
46
  "devDependencies": {
47
47
  "@cap-js/cds-types": "^0",
48
- "@sap/cds": "^8",
49
48
  "@stylistic/eslint-plugin-js": "^4.2.0",
50
49
  "acorn": "^8.10.0",
51
50
  "eslint": "^9",
@@ -58,7 +57,7 @@
58
57
  "inline_declarations": "flat",
59
58
  "target_module_type": "auto",
60
59
  "properties_optional": true,
61
- "use_entities_proxy": false,
60
+ "use_entities_proxy": true,
62
61
  "build_task": true
63
62
  },
64
63
  "schema": {
@@ -71,6 +70,15 @@
71
70
  "type": "object",
72
71
  "description": "Configuration for CDS Typer",
73
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
+ },
74
82
  "output_directory": {
75
83
  "type": "string",
76
84
  "description": "Root directory to write the generated files to.",
@@ -101,7 +109,7 @@
101
109
  "use_entities_proxy": {
102
110
  "type": "boolean",
103
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",
104
- "default": false
112
+ "default": true
105
113
  },
106
114
  "inline_declarations": {
107
115
  "type": "string",
@@ -114,7 +122,7 @@
114
122
  },
115
123
  "properties_optional": {
116
124
  "type": "boolean",
117
- "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).",
118
126
  "default": true
119
127
  },
120
128
  "ieee754compatible": {