@sap/cds-dk 4.6.2 → 4.7.2

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.
Files changed (39) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/bin/add.js +2 -1
  3. package/bin/bind.js +121 -48
  4. package/bin/cds.js +8 -0
  5. package/bin/env.js +0 -2
  6. package/bin/repl.js +5 -2
  7. package/bin/watch.js +11 -8
  8. package/lib/bind/bindingResolver.js +26 -14
  9. package/lib/bind/bindingUtil.js +144 -0
  10. package/lib/bind/cfKeyProvider.js +2 -80
  11. package/lib/bind/kubernetesCredentialsProvider.js +157 -0
  12. package/lib/bind/userCdsrcJsonStorageProvider.js +7 -7
  13. package/lib/client/activate.js +9 -17
  14. package/lib/client/auth_manager.js +1 -1
  15. package/lib/client/extend.js +4 -9
  16. package/lib/import/index.js +3 -2
  17. package/lib/import/internalDoc/edm2cdsTypeMapping.md +38 -0
  18. package/lib/import/utils/MultiMap.js +1 -0
  19. package/lib/import/v2parser.js +102 -37
  20. package/lib/import/v4parser.js +59 -20
  21. package/lib/index.js +6 -5
  22. package/lib/init/constants.js +2 -2
  23. package/lib/init/index.js +1 -0
  24. package/lib/init/template/data/index.js +29 -7
  25. package/lib/init/template/hana/files/common/undeploy.json +5 -0
  26. package/lib/init/template/hana/files/java/package.json +3 -0
  27. package/lib/init/template/hana/index.js +19 -23
  28. package/lib/init/template/lint/index.js +17 -1
  29. package/lib/init/template/mta/files/mta.yaml.hbs +12 -5
  30. package/lib/init/template/mta/projectReader.js +1 -3
  31. package/lib/init/template/samples/index.js +1 -1
  32. package/lib/init/template/templateBase.js +10 -41
  33. package/lib/init/util/fsUtil.js +1 -1
  34. package/lib/init/util/mvnArchetypeUtil.js +3 -1
  35. package/lib/lint/Linter.js +6 -13
  36. package/lib/lint/io.js +21 -31
  37. package/lib/watch/node-watch.js +46 -20
  38. package/npm-shrinkwrap.json +10648 -5083
  39. package/package.json +3 -3
@@ -11,16 +11,12 @@ module.exports = class DataTemplate extends TemplateBase {
11
11
  async run() {
12
12
  const env = await this.getEnv();
13
13
 
14
- const prefix = (typeof this.options.for === 'string') // --for <prefix>
15
- ? this.options.for
16
- : '';
17
- const prefixedWith = (definition) => definition.name === prefix || definition.name.startsWith(prefix);
18
-
19
14
  let dest = (typeof this.options.out === 'string') // target folder
20
15
  ? this.options.out
21
16
  : getDefaultTargetFolder(env, this.projectPath);
22
17
  dest = resolve(this.projectPath, dest);
23
18
 
19
+ const nameFilter = asRegex(this.options.for)
24
20
  const force = this.options.force;
25
21
  const logger = this.logger;
26
22
 
@@ -29,18 +25,44 @@ module.exports = class DataTemplate extends TemplateBase {
29
25
 
30
26
  // Also include 'external' entities w/ cds.persistence.skip (similar to 'mocked' option of serve).
31
27
  // Could be too much sometimes, but don't want to bother users w/ a separate option for this.
32
- cds.deploy.include_external_entities_in(csn)
28
+ includeExternalEntities(csn, logger)
33
29
 
34
30
  csn = cds.compile.for.sql(csn, { names:env.sql.names, messages:[] }) // CSN w/ persistence information
35
31
  csn = cds.reflect(csn); // reflected model (adds additional helper functions)
36
32
 
37
33
  csn.all('entity')
38
- .filter(prefixedWith)
34
+ .filter (e => e.name.match(nameFilter)) // --for prefix|regex
39
35
  .filter (e => !e.query) // exclude entities with queries (projection on, select from ...)
40
36
  .filter (e => e.name !== 'DRAFT.DraftAdministrativeData' && !e.name.endsWith('.drafts')) // exclude draft stuff
41
37
  .forEach(e => processEntity(e, dest, this.projectPath, csn, force, logger));
42
38
  }
39
+ }
40
+
41
+ function includeExternalEntities(model, logger) {
42
+ for (let each in model.definitions) {
43
+ const def = model.definitions[each]
44
+ if (def['@cds.persistence.skip'] === true) {
45
+ logger.debug ('Including skipped entity '+each)
46
+ delete def['@cds.persistence.skip']
47
+ }
48
+ }
49
+ return model
50
+ }
43
51
 
52
+ function asRegex(spec) {
53
+ if (typeof spec === 'string') {
54
+ try {
55
+ if (spec.match(/[\^$|*]/))
56
+ return new RegExp(spec)
57
+ else { // no meta chars -> prefix semantics
58
+ spec = spec.replace(/\./g, '\\.') // escape dot
59
+ return new RegExp('^'+spec+'.*')
60
+ }
61
+ } catch (err) {
62
+ throw new Error (err.message) // user error, so cut off stack trace
63
+ }
64
+ }
65
+ return /.*/
44
66
  }
45
67
 
46
68
  function processEntity (entity, dest, projectPath, csn, force, logger) {
@@ -0,0 +1,5 @@
1
+ [
2
+ "src/gen/**/*.hdbview",
3
+ "src/gen/**/*.hdbindex",
4
+ "src/gen/**/*.hdbconstraint"
5
+ ]
@@ -3,6 +3,9 @@
3
3
  "dependencies": {
4
4
  "@sap/hdi-deploy": "^4"
5
5
  },
6
+ "engines": {
7
+ "node": "^16"
8
+ },
6
9
  "scripts": {
7
10
  "start": "node node_modules/@sap/hdi-deploy/deploy.js"
8
11
  }
@@ -13,8 +13,6 @@ const { nullLogger } = require('../../util/logger');
13
13
  module.exports = class HanaTemplate extends TemplateBase {
14
14
  constructor(projectPath, generator) {
15
15
  super(projectPath, generator, __dirname);
16
-
17
- this.env = null;
18
16
  }
19
17
 
20
18
  async checkEnabled() {
@@ -22,34 +20,32 @@ module.exports = class HanaTemplate extends TemplateBase {
22
20
  return true;
23
21
  }
24
22
 
25
- this.env = await this.getEnv(true);
23
+ const env = await this.getEnv();
26
24
 
27
- const hdiConfigPath = path.join(this.projectPath, this.env.folders.db, 'src/.hdiconfig');
28
- if (await this.fsUtil.pathExists(hdiConfigPath)) {
29
- const configFilePath = path.join(this.env.folders.db, 'src/.hdiconfig');
30
- throw new Error(`Database config file ${configFilePath} already exists. Use --force to overwrite.`);
31
- }
25
+ await Promise.all(['src/.hdiconfig', 'undeploy.json'].map(async(filePath) => {
26
+ if (await this.fsUtil.pathExists(path.join(this.projectPath, env.folders.db, filePath))) {
27
+ throw new Error(`File ${path.join(env.folders.db, filePath)} already exists. Use --force to overwrite.`);
28
+ }
29
+ }))
32
30
 
33
31
  return true;
34
32
  }
35
33
 
36
34
  // read env from project
37
- async getEnv(force) {
35
+ async getEnv() {
38
36
  const cdsEnvVar = process.env.CDS_ENV;
39
37
  process.env.CDS_ENV = 'production'
40
- let env
41
38
  try {
42
- env = await super.getEnv(force);
39
+ return await super.getEnv();
43
40
  } finally {
44
41
  cdsEnvVar ? process.env.CDS_ENV = cdsEnvVar : delete process.env.CDS_ENV
45
42
  }
46
- return env
47
43
  }
48
44
 
49
45
  async run() {
50
- this.env = await this.getEnv(true);
46
+ const env = await this.getEnv();
51
47
 
52
- await this.templateUtil.copyFiles('common', path.join(this.projectPath, this.env.folders.db), {
48
+ await this.templateUtil.copyFiles('common', path.join(this.projectPath, env.folders.db), {
53
49
  projectName: this.projectName
54
50
  }, this.options.force);
55
51
 
@@ -57,13 +53,13 @@ module.exports = class HanaTemplate extends TemplateBase {
57
53
  const projectType = await this.getProjectType();
58
54
  switch (projectType) {
59
55
  case PROJECT_TYPE.java:
60
- await this.templateUtil.copyFiles('java', path.join(this.projectPath, this.env.folders.db), {}, this.options.force);
56
+ await this.templateUtil.copyFiles('java', path.join(this.projectPath, env.folders.db), {}, this.options.force);
61
57
  await this._updatePomXml();
62
- await this._updateCdsrcJson(cdsrcJsonPath, this.env.folders.db);
58
+ await this._updateCdsrcJson(cdsrcJsonPath, env.folders.db, env);
63
59
  break;
64
60
 
65
61
  case PROJECT_TYPE.nodejs:
66
- await this._updatePackageJson(path.join(this.projectPath, 'package.json'), this.env.folders.db);
62
+ await this._updatePackageJson(path.join(this.projectPath, 'package.json'), env.folders.db, env);
67
63
  break;
68
64
 
69
65
  default: break;
@@ -87,7 +83,7 @@ module.exports = class HanaTemplate extends TemplateBase {
87
83
  }
88
84
  }
89
85
 
90
- async _updatePackageJson(packageJsonPath, modelName) { //NOSONAR
86
+ async _updatePackageJson(packageJsonPath, modelName, env) { //NOSONAR
91
87
  modelName = path.basename(modelName);
92
88
 
93
89
  if (!await this.fsUtil.pathExists(packageJsonPath)) {
@@ -109,7 +105,7 @@ module.exports = class HanaTemplate extends TemplateBase {
109
105
  changed = true
110
106
  }
111
107
 
112
- if (!this._isUsingHana(modelName)) {
108
+ if (!this._isUsingHana(modelName, env)) {
113
109
  packageJson.cds = packageJson.cds || {};
114
110
  packageJson.cds.requires = packageJson.cds.requires || {};
115
111
  packageJson.cds.requires[modelName] = packageJson.cds.requires[modelName] || {};
@@ -130,7 +126,7 @@ module.exports = class HanaTemplate extends TemplateBase {
130
126
  }
131
127
  }
132
128
 
133
- async _updateCdsrcJson(cdsrcJsonPath, modelName) {
129
+ async _updateCdsrcJson(cdsrcJsonPath, modelName, env) {
134
130
  modelName = path.basename(modelName);
135
131
 
136
132
  let changed;
@@ -143,7 +139,7 @@ module.exports = class HanaTemplate extends TemplateBase {
143
139
 
144
140
  this.logger.isDebug && this.logger.debug(`_updateCdsrcJson before: ${JSON.stringify(cdsrcJson, null, 2)}`);
145
141
 
146
- if (!this._isUsingHana(modelName)) {
142
+ if (!this._isUsingHana(modelName, env)) {
147
143
  cdsrcJson.requires = cdsrcJson.requires || {};
148
144
  cdsrcJson.requires[modelName] = cdsrcJson.requires[modelName] || {};
149
145
  cdsrcJson.requires[modelName].kind = KIND_SQL;
@@ -161,7 +157,7 @@ module.exports = class HanaTemplate extends TemplateBase {
161
157
  }
162
158
  }
163
159
 
164
- _isUsingHana(db) {
165
- return this.env.get(`requires.${db}.kind`) === KIND_HANA || this.env.get(`requires.${db}.dialect`) === KIND_HANA || this.env.get(`requires.${db}.use`) === KIND_HANA;
160
+ _isUsingHana(db, env) {
161
+ return env.get(`requires.${db}.kind`) === KIND_HANA || env.get(`requires.${db}.dialect`) === KIND_HANA || env.get(`requires.${db}.use`) === KIND_HANA;
166
162
  }
167
163
  }
@@ -9,10 +9,15 @@ const readline = require("readline");
9
9
 
10
10
  const { OPTION_LINT_DEV, OPTION_LINT_GLOBAL } = require("../../constants");
11
11
  const { mkdirp, fs } = require("@sap/cds/lib/utils");
12
- const { getConfigPath } = require("@sap/eslint-plugin-cds/lib/api");
12
+ const { getConfigPath, getFileExtensions } = require("@sap/eslint-plugin-cds/lib/api");
13
13
  const generators = require("../../../lint/generators");
14
14
 
15
15
  const IS_WIN = os.platform() === "win32";
16
+ const ALLOWED_FILE_EXTENSIONS = getFileExtensions();
17
+ const VSCODE_FILE_ASSOCIATIONS = {
18
+ "csv": [ "csv", "csv (semicolon)", "tsv", "tab" ]
19
+ }
20
+
16
21
  module.exports = class LintTemplate extends TemplateBase {
17
22
  constructor(projectPath, generator) {
18
23
  super(projectPath, generator, __dirname);
@@ -68,10 +73,21 @@ module.exports = class LintTemplate extends TemplateBase {
68
73
  }
69
74
 
70
75
  // Generate VS Code extension settings
76
+ const extensions = ALLOWED_FILE_EXTENSIONS.map((ext) => {
77
+ return path.extname(ext).replace('.', '');
78
+ })
79
+ extensions.forEach((ext) => {
80
+ if (ext in VSCODE_FILE_ASSOCIATIONS) {
81
+ VSCODE_FILE_ASSOCIATIONS[ext].forEach((assoc) => {
82
+ extensions.push(assoc)
83
+ })
84
+ }
85
+ });
71
86
  await io.sanitizeVscodeSettings(
72
87
  this.projectPath,
73
88
  path.join(this.projectPath, ".vscode/settings.json"),
74
89
  this.lintType,
90
+ extensions,
75
91
  this.customRuleExample
76
92
  );
77
93
  }
@@ -13,7 +13,12 @@ build-parameters:
13
13
  before-all:
14
14
  - builder: custom
15
15
  commands:
16
+ {{#cap.isNodejs}}
17
+ - npm ci
18
+ {{/cap.isNodejs}}
19
+ {{#cap.isJava}}
16
20
  - npm install --production
21
+ {{/cap.isJava}}
17
22
  - npx -p @sap/cds-dk cds build --production
18
23
 
19
24
  {{! This template creates one or two deployable modules:
@@ -30,6 +35,8 @@ modules:
30
35
  {{#cap.isNodejs}}
31
36
  parameters:
32
37
  buildpack: nodejs_buildpack
38
+ build-parameters:
39
+ builder: npm-ci
33
40
  {{/cap.isNodejs}}
34
41
  {{#cap.isJava}}
35
42
  parameters:
@@ -207,11 +214,11 @@ resources:
207
214
  type: org.cloudfoundry.managed-service
208
215
  requires:
209
216
  - name: {{cap.appName}}-uaa-mt
210
- - name: mtx-api
211
- properties:
212
- prop: ~{mtx-url}
213
- parameters:
214
- param: ~{mtx-url}
217
+ - name: mtx-api
218
+ properties:
219
+ prop: ~{mtx-url}
220
+ parameters:
221
+ param: ~{mtx-url}
215
222
  parameters:
216
223
  service: saas-registry
217
224
  service-plan: application
@@ -22,13 +22,11 @@ module.exports = class ProjectReader {
22
22
  async getEnv(profile = 'production') {
23
23
  const cdsEnvVar = process.env.CDS_ENV
24
24
  process.env.CDS_ENV = profile
25
- let env
26
25
  try {
27
- env = await this.template.getEnv(true);
26
+ return await this.template.getEnv();
28
27
  } finally {
29
28
  cdsEnvVar ? process.env.CDS_ENV = cdsEnvVar : delete process.env.CDS_ENV
30
29
  }
31
- return env
32
30
  }
33
31
 
34
32
  get fsUtil() {
@@ -58,7 +58,7 @@ module.exports = class SamplesTemplate extends TemplateBase {
58
58
  }
59
59
 
60
60
  async _addNodejsSamples() {
61
- const folders = (await this.getEnv(true)).folders;
61
+ const folders = (await this.getEnv()).folders;
62
62
  const dbFolder = path.join(this.projectPath, folders.db);
63
63
  await this.templateUtil.copyFiles('nodejs/db', dbFolder, {}, this.options.force);
64
64
 
@@ -1,11 +1,9 @@
1
1
  const path = require('path');
2
- const cds = require('@sap/cds/lib');
2
+ const Config = require('@sap/cds/lib/env');
3
3
 
4
4
  const TemplateUtil = require('../util/templateUtil');
5
- const commandUtil = require('../util/commandUtil');
6
- const { nullLogger } = require('../util/logger');
7
5
 
8
- const { IS_WIN, PROJECT_TYPE } = require('../constants');
6
+ const { PROJECT_TYPE } = require('../constants');
9
7
 
10
8
 
11
9
  module.exports = class TemplateBase {
@@ -43,49 +41,20 @@ module.exports = class TemplateBase {
43
41
  return true;
44
42
  }
45
43
 
46
- getCdsModulePath() {
47
- return path.join(__dirname, '../../../bin/cds.js');
48
- }
49
-
50
44
  /**
51
45
  * calls cds env and caches result
52
46
  * @param {boolean} force, ignore cached value
53
47
  * @returns {object} the current cds env
54
48
  */
55
- async getEnv(force = false) {
56
- if (!this.env || force) {
57
- let projectPath;
58
- if (await this.fsUtil.pathExists(this.projectPath)) {
59
- projectPath = this.projectPath;
60
- } else {
61
- projectPath = this.cwd;
62
- }
63
-
64
- const logger = (this.logger.isDebug ? this.logger : nullLogger);
65
-
66
- let modPath = this.getCdsModulePath();
67
- if (IS_WIN) {
68
- modPath = `"${modPath}"`;
69
- }
70
-
71
- let envJsonOutput = await commandUtil.spawnCommand('node', [modPath, 'env', 'json'], {
72
- cwd: projectPath,
73
- env: process.env
74
- }, logger, true);
75
-
76
- if (envJsonOutput) {
77
- // cheap check to see if json is valid
78
- if (envJsonOutput.indexOf('":') < 0) {
79
- // eslint-disable-next-line no-control-regex
80
- envJsonOutput = envJsonOutput.replace(/'/g, '"').replace(/\s(\w+):\s/g, ' "$1": ').replace(/\x1b\[\w+m/g, '');
81
- }
82
-
83
- this.env = JSON.parse(envJsonOutput);
84
- // env has additional getters and setters
85
- Object.setPrototypeOf(this.env, cds.env.__proto__);
86
- }
49
+ async getEnv() {
50
+ let projectPath;
51
+ if (await this.fsUtil.pathExists(this.projectPath)) {
52
+ projectPath = this.projectPath;
53
+ } else {
54
+ projectPath = this.cwd;
87
55
  }
88
- return this.env;
56
+
57
+ return Config.for('cds', projectPath);
89
58
  }
90
59
 
91
60
  /**
@@ -18,7 +18,7 @@ module.exports = class FsUtil {
18
18
  await fse.outputFile(filepath, content, UTF_8);
19
19
  }
20
20
 
21
- async writeJSON(filepath, object, options = { spaces: 4 }, skipFileLog = false) {
21
+ async writeJSON(filepath, object, options = { spaces: 2 }, skipFileLog = false) {
22
22
  if (!skipFileLog) {
23
23
  this._addTouchedFile(filepath);
24
24
  }
@@ -31,7 +31,9 @@ class MvnArchetypeUtil {
31
31
 
32
32
  params.set('-B');
33
33
 
34
- defaultLogger.isDebug && params.set('-X');
34
+ if (defaultLogger.isDebug) {
35
+ params.set('-X'); params.set('-e');
36
+ }
35
37
 
36
38
  return Array.from(params, (e) => {
37
39
  let result = e[0];
@@ -23,7 +23,6 @@ class Linter {
23
23
  this.eslintCmdOpts = {};
24
24
  this.eslintCmdOptsIndex = 0;
25
25
  this.eslintCmdFileExpr = "";
26
- this.formatter = false;
27
26
  this.isFile = false;
28
27
  this.help = "";
29
28
  this.debug = "";
@@ -141,16 +140,14 @@ class Linter {
141
140
 
142
141
  _addExtensions() {
143
142
  // Add CDS file extensions to lint
144
- const extensions = ALLOWED_FILE_EXTENSIONS.map((ext) => ext.replace('*.', '.'));
143
+ const extensions = ALLOWED_FILE_EXTENSIONS.map((ext) => path.extname(ext));
145
144
  this.eslintCmdOpts = io.mergeWithUserOpts(
146
145
  this.eslintCmdOpts,
147
146
  "ext",
148
147
  extensions.join(',')
149
148
  );
150
149
  // Enable colored output
151
- if (process.stdout.isTTY) {
152
- this.eslintCmdOpts = io.mergeWithUserOpts(this.eslintCmdOpts, "color");
153
- }
150
+ this.eslintCmdOpts = io.mergeWithUserOpts(this.eslintCmdOpts, "color");
154
151
  return;
155
152
  }
156
153
 
@@ -205,22 +202,18 @@ class Linter {
205
202
  } else {
206
203
  input = `'${this.eslintCmdFileExpr.join("' '")}'`;
207
204
  }
208
- const eslintCmdSplit = this.eslintCmd.split(" ");
209
- this.eslintCmd = eslintCmdSplit[0];
210
- eslintCmdOpts.unshift(eslintCmdSplit[1]);
211
- eslintCmdOpts.push(input);
212
205
  // Run ESLint with given cmd line options
213
- // - Show 'eslint' instead of 'node ...eslint.js' cmd
214
- // - Remove debug flag for printed cmd
206
+ // - Show 'eslint' instead of actual eslint cmd ('node .../.eslint.js')
207
+ // - Remove debug flag for printed cmd line
215
208
  if (this.eslintCmdOpts.debug) {
216
209
  eslintCmdOpts.splice(eslintCmdOpts.indexOf('--debug'), 1);
217
210
  console.log(
218
- `Linting:\n ${term.info(`eslint ${eslintCmdOpts.slice(1).join(' ')}`)}\n`
211
+ `Linting:\n ${term.info(`eslint ${eslintCmdOpts.concat(input).join(' ')}`)}\n`
219
212
  );
220
213
  eslintCmdOpts.push('--debug');
221
214
  }
222
215
  if (!dryRun) {
223
- return commandUtil.spawnCommand(this.eslintCmd, eslintCmdOpts, {
216
+ return commandUtil.spawnCommand(this.eslintCmd, eslintCmdOpts.concat(input), {
224
217
  cwd: process.cwd(),
225
218
  shell: true,
226
219
  stdio: process.env.NODE_ENV === 'test' ? 'pipe' : 'inherit',
package/lib/lint/io.js CHANGED
@@ -22,7 +22,7 @@ module.exports = {
22
22
  case ".eslintrc.js":
23
23
  case ".eslintrc.cjs":
24
24
  try {
25
- config = JSON.parse(configString.replace(/^module\.exports =/, ""));
25
+ config = JSONC.parse((configString.replace(/^module\.exports =/, "")));
26
26
  } catch (err) {
27
27
  config = {};
28
28
  }
@@ -33,14 +33,14 @@ module.exports = {
33
33
  break;
34
34
  case ".eslintrc.json":
35
35
  case ".eslintrc":
36
+ config = await fsUtil.readJSONC(configPath);
37
+ break;
36
38
  case "package.json":
37
- try {
38
- config = JSON.parse(await fsp.readFile(configPath, "utf8"));
39
- if (configFile === "package.json" && "eslintConfig" in config) {
40
- config = config["eslintConfig"];
41
- }
42
- } catch (err) {
43
- // Do nothing
39
+ config = JSON.parse(await fsp.readFile(configPath, "utf8"));
40
+ if ("eslintConfig" in config) {
41
+ config = config["eslintConfig"];
42
+ } else {
43
+ config = {}
44
44
  }
45
45
  break;
46
46
  default:
@@ -138,21 +138,20 @@ module.exports = {
138
138
  break;
139
139
  case ".eslintrc.json":
140
140
  case ".eslintrc":
141
+ await fsUtil.writeFile(configPath, `${JSON.stringify(configContents, null, 2)}`);
142
+ break;
141
143
  case "package.json":
142
- if (configFile === "package.json") {
143
- if (await fsUtil.pathExists(configPath)) {
144
- contents = JSON.parse(await fsp.readFile(configPath, "utf8"));
145
- } else {
146
- contents = {};
147
- }
148
- if (!("eslintConfig" in configContents)) {
149
- contents["eslintConfig"] = configContents;
150
- } else {
151
- contents["eslintConfig"] = [...configContents, ...contents["eslintConfig"]];
152
- }
153
- configContents = contents;
144
+ if (await fsUtil.pathExists(configPath)) {
145
+ contents = JSON.parse(await fsp.readFile(configPath, "utf8"));
146
+ } else {
147
+ contents = {};
154
148
  }
155
- await fsUtil.writeFile(configPath, `${JSON.stringify(configContents, null, 2)}`);
149
+ if (!("eslintConfig" in configContents)) {
150
+ contents["eslintConfig"] = configContents;
151
+ } else {
152
+ contents["eslintConfig"] = [...configContents, ...contents["eslintConfig"]];
153
+ }
154
+ await fsUtil.writeFile(configPath, `${JSON.stringify(contents, null, 2)}`);
156
155
  break;
157
156
  default:
158
157
  break;
@@ -202,18 +201,9 @@ module.exports = {
202
201
  * (3) "Global" case adds configFile and removes rulePaths
203
202
  * @param {*} settingsPath VS Code settings file
204
203
  */
205
- async sanitizeVscodeSettings(projectPath, settingsPath, lintType, customRuleExample = false) {
204
+ async sanitizeVscodeSettings(projectPath, settingsPath, lintType, lintFileTypes, customRuleExample = false) {
206
205
  let settings = (await this.readVscodeSettings(settingsPath)) || {};
207
- const lintFileTypes = ["cds", "csn"];
208
206
  const rulePaths = [path.join(".eslint/rules")];
209
- const configFile = path.join(__dirname, ".eslintrc.js");
210
-
211
- function _addArrayOrString(array) {
212
- if (array.length === 1) {
213
- return array[0];
214
- }
215
- return array;
216
- }
217
207
 
218
208
  if (settings["eslint.validate"]) {
219
209
  settings["eslint.validate"] = this.mergeArrays(settings["eslint.validate"], lintFileTypes);
@@ -1,3 +1,5 @@
1
+ const DEBUG = /\bwatch\b/.test(process.env.DEBUG) && console.debug
2
+
1
3
  module.exports = ({ cwd, script, ext, ignore=RegExp(), env={}, delay=200, options={} })=>{
2
4
 
3
5
  let child
@@ -5,19 +7,23 @@ module.exports = ({ cwd, script, ext, ignore=RegExp(), env={}, delay=200, option
5
7
  // Phoning home...
6
8
  const EventEmitter = require('events')
7
9
  const emitter = new EventEmitter
8
- process.on('exit', () => { gracefullyKill (child); emitter.emit('quit') })
10
+ process.on('exit', () => { _kill (child); emitter.emit('quit') })
9
11
 
10
12
  // Re-starting child process...
11
13
  const { fork } = require('child_process')
12
- const restart = (evt, name)=>{
13
- gracefullyKill (child, () => {
14
+ const restart = _coalesceEvents(delay, (events) => {
15
+ DEBUG && DEBUG('>> Events:', events)
16
+ _kill (child, () => {
14
17
  const execArgv = [...process.execArgv]
15
- if (evt && evt.startsWith('--')) execArgv.push(evt)
16
- if (name && name.startsWith('--')) execArgv.push(name)
18
+ events.forEach(evt => {
19
+ if (evt.type && evt.type.startsWith('--')) execArgv.push(evt.type)
20
+ if (evt.name && evt.name.startsWith('--')) execArgv.push(evt.name)
21
+ })
17
22
  if (options.debug) execArgv.push('--inspect')
18
23
  if (options['debug-brk']) execArgv.push('--inspect-brk')
19
- const files = evt === "update" ? [ name ] : undefined
20
- emitter.emit ('restart', files)
24
+
25
+ const updated = events.filter(evt => evt.type && evt.type === 'update').map(evt => evt.name)
26
+ emitter.emit ('restart', updated)
21
27
  child = fork (script, { cwd, env, stdio:'inherit', execArgv }, (err)=>{
22
28
  if (err) console.error (err)
23
29
  })
@@ -26,14 +32,16 @@ module.exports = ({ cwd, script, ext, ignore=RegExp(), env={}, delay=200, option
26
32
  emitter.emit('message', msg)
27
33
  })
28
34
  })
29
- }
35
+
36
+ })
37
+
30
38
  restart()
31
39
 
32
40
  // Watching for touched files...
33
41
  const watch = require('node-watch')
34
42
  const include = RegExp(`\\.(${ext.replace(/,/g,'|')})$`)
35
43
  const filter = f => !ignore.test(f) && include.test(f)
36
- watch (process.cwd(),{ recursive:true, filter, delay }, restart)
44
+ watch (process.cwd(),{ recursive:true, filter, delay:0 }, restart)
37
45
 
38
46
  // Live commands...
39
47
  const readline = require('readline')
@@ -44,12 +52,12 @@ module.exports = ({ cwd, script, ext, ignore=RegExp(), env={}, delay=200, option
44
52
  else if (input === 'break' || input === 'brk') restart('--inspect-brk')
45
53
  else if (input === 'debug-brk') restart('--inspect-brk')
46
54
  else if (input === 'ps') ps(child,env)
47
- else if (input === 'bye' || input.match(/^n$/i)) { gracefullyKill (child, () => process.exit()) }
55
+ else if (input === 'bye' || input.match(/^n$/i)) { _kill (child, () => process.exit()) }
48
56
  else console.log ('?\n')
49
57
  })
50
58
 
51
59
  emitter.restart = restart
52
- emitter.stop = () => { gracefullyKill(child, () => false); child = undefined; }
60
+ emitter.stop = () => { _kill(child, () => false); child = undefined; }
53
61
  emitter.setEnv = (name, value) => {
54
62
  if (typeof value === "undefined") {
55
63
  delete env[name]
@@ -67,14 +75,32 @@ const ps = (child,env) => console.log (`\x1b[32m
67
75
  \x1b[0m`
68
76
  )
69
77
 
70
- // On Windows, kill() abruptly kills the child process (https://nodejs.org/api/child_process.html#subprocesskillsignal)
71
- // Send a custom message an wait a bit to give cleanup code a chance to execute.
72
- const isWindows = (process.platform === 'win32')
73
- const waitTime = parseInt(process.env.CDS_WATCH_KILL_DELAY) || (isWindows ? 500 : 0)
74
- function gracefullyKill (proc, fn=()=>{}) {
75
- if (proc) {
76
- proc.send({ exit: true })
77
- setTimeout(()=> { proc.kill(); fn()}, waitTime)
78
+ // reduces mutiple node-watch events in a time frame into a bulk
79
+ function _coalesceEvents(delay, fn) {
80
+ let timer, cache = []
81
+ function handle() {
82
+ fn(cache)
83
+ timer = null
84
+ cache = []
85
+ }
86
+ return function(type, name) { // node-watch callback signature
87
+ cache.push({type, name})
88
+ if (!timer) {
89
+ timer = setTimeout(handle, delay);
90
+ }
91
+ }
92
+ }
93
+
94
+ function _kill (proc, fn=()=>{}) {
95
+ if (!proc) return fn()
96
+
97
+ // On Windows, child_process.kill() abruptly kills the process (https://nodejs.org/api/child_process.html#subprocesskillsignal)
98
+ // Server has cleanup code to run, so send a custom message and give it a bit time.
99
+ if (process.platform === 'win32') {
100
+ proc.send({ exit: true }, () => {})
101
+ const waitTime = parseInt(process.env.CDS_WATCH_KILL_DELAY) || 500
102
+ setTimeout(()=> { proc.kill(); fn() }, waitTime)
103
+ } else {
104
+ proc.kill(); fn()
78
105
  }
79
- else fn()
80
106
  }