@sap/cds-dk 9.8.3 → 9.9.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/CHANGELOG.md +22 -0
- package/bin/add.js +3 -3
- package/bin/cds.js +16 -2
- package/bin/import.js +6 -2
- package/bin/logout.js +9 -12
- package/lib/bind/shared.js +2 -2
- package/lib/build/constants.js +0 -1
- package/lib/build/index.js +420 -463
- package/lib/build/plugins/hana/index.js +121 -2
- package/lib/build/plugins/helm/files/subcharts/content-deployment/Chart.yaml +1 -1
- package/lib/build/plugins/helm/files/subcharts/content-deployment/templates/job.yaml +1 -1
- package/lib/build/plugins/helm/files/subcharts/content-deployment/values.schema.json +4 -0
- package/lib/build/plugins/helm/files/subcharts/content-deployment/values.yaml +5 -2
- package/lib/build/plugins/internal.js +1 -1
- package/lib/build/plugins/mtx-extension/index.js +0 -2
- package/lib/deploy/index.js +23 -3
- package/lib/deploy/to-hana/hana.js +24 -15
- package/lib/import/common.js +6 -3
- package/lib/init/constants.js +3 -1
- package/lib/init/indent.js +0 -1
- package/lib/init/merge.js +0 -1
- package/lib/init/mvn.js +4 -2
- package/lib/init/projectReader.js +0 -1
- package/lib/init/registries/mta.js +4 -2
- package/lib/init/template/app-frontend/index.js +39 -4
- package/lib/init/template/data/as-csv.js +2 -0
- package/lib/init/template/esm/index.js +5 -0
- package/lib/init/template/extensibility/index.js +1 -1
- package/lib/init/template/hana/index.js +1 -1
- package/lib/init/template/html5-repo/files/mta.yaml.hbs +9 -20
- package/lib/init/template/http/index.js +34 -6
- package/lib/init/template/kyma/index.js +1 -0
- package/lib/init/template/postgres/files/values.yaml.hbs +1 -1
- package/lib/init/template/postgres/index.js +1 -1
- package/lib/init/template/react/files/index.html +13 -0
- package/lib/init/template/react/files/package.json.hbs +22 -0
- package/lib/init/template/react/files/public/favicon.svg +1 -0
- package/lib/init/template/react/files/src/App.jsx.hbs +29 -0
- package/lib/init/template/react/files/src/index.css +28 -0
- package/lib/init/template/react/files/src/main.jsx +10 -0
- package/lib/init/template/react/files/vite.config.js +19 -0
- package/lib/init/template/react/index.js +29 -0
- package/lib/init/template/sample/index.js +1 -4
- package/lib/init/template/typer/index.js +1 -1
- package/lib/init/template/typescript/files/package.json +1 -1
- package/lib/init/template/typescript/index.js +1 -1
- package/lib/init/template/ui5/index.js +2 -1
- package/lib/init/template/vue/files/index.html +13 -0
- package/lib/init/template/vue/files/package.json.hbs +19 -0
- package/lib/init/template/vue/files/public/favicon.svg +1 -0
- package/lib/init/template/vue/files/src/App.vue.hbs +34 -0
- package/lib/init/template/vue/files/src/main.js +5 -0
- package/lib/init/template/vue/files/src/style.css +20 -0
- package/lib/init/template/vue/files/vite.config.js +19 -0
- package/lib/init/template/vue/index.js +29 -0
- package/lib/init/template/workzone-standard/files/mta.yaml.hbs +2 -13
- package/lib/init/xml.js +0 -2
- package/lib/mtx/auth_manager.js +21 -11
- package/lib/mtx/download_legacy.js +9 -8
- package/lib/mtx/pull.js +9 -8
- package/lib/mtx/push.js +42 -28
- package/lib/mtx/settings_manager.js +17 -10
- package/lib/mtx/tenant_action.js +9 -9
- package/lib/mtx/util/request.js +158 -0
- package/lib/util/cf.js +30 -29
- package/lib/watch/node-watch.js +1 -1
- package/lib/watch/vite-watch.js +76 -0
- package/lib/watch/watched.js +5 -4
- package/npm-shrinkwrap.json +25 -165
- package/package.json +1 -2
- package/lib/init/template/sample/files/db/hana/index.cds +0 -10
- package/lib/init/template/sample/files/db/sqlite/index.cds +0 -10
- package/lib/init/template/sample/files/package.json.hbs +0 -14
- package/lib/util/axios.js +0 -118
package/lib/mtx/push.js
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
const { utils: { local }} = require ('../cds');
|
|
2
2
|
const { join } = require('path');
|
|
3
3
|
const { readFileSync, existsSync } = require('fs');
|
|
4
|
-
const axios = require('axios');
|
|
5
4
|
|
|
6
5
|
const { login } = require('./auth_manager');
|
|
7
6
|
const buildSystem = require('../build')
|
|
8
7
|
|
|
9
|
-
require('
|
|
8
|
+
const { buildResponseError, buildNetworkError, mtxFetch } = require('./util/request');
|
|
10
9
|
const { getMessage } = require('./util/logging');
|
|
11
10
|
const { schemaRegex } = require('./util/urls');
|
|
12
|
-
const { handleHttpError } = require('./util/errors');
|
|
13
11
|
|
|
14
12
|
const JOB_STATUS = {
|
|
15
13
|
QUEUED: 'QUEUED',
|
|
@@ -35,9 +33,9 @@ module.exports = class Push {
|
|
|
35
33
|
const target = { url, ...subdomain && { subdomain } };
|
|
36
34
|
|
|
37
35
|
console.log(`\nPushing extension '${extensionName}' from`, { src }, 'to', target);
|
|
38
|
-
const
|
|
36
|
+
const pushResult = await this.pushTgz(params, content, extensionName);
|
|
39
37
|
if (params.get('async')) {
|
|
40
|
-
await this.pollUntilFinished(
|
|
38
|
+
await this.pollUntilFinished(pushResult.headers.get('location'), params)
|
|
41
39
|
}
|
|
42
40
|
console.log ('Activation succeeded.\n')
|
|
43
41
|
}
|
|
@@ -52,11 +50,8 @@ module.exports = class Push {
|
|
|
52
50
|
if (!schemaRegex.test(src)) {
|
|
53
51
|
throw getMessage(`Non-existent path: ${src}`, { command: 'push' });
|
|
54
52
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
} catch (error) {
|
|
58
|
-
handleHttpError(error, params, { url: src });
|
|
59
|
-
}
|
|
53
|
+
const response = await mtxFetch('GET', src, {}, params);
|
|
54
|
+
content = Buffer.from(await response.arrayBuffer()).toString('base64');
|
|
60
55
|
}
|
|
61
56
|
return { src, content };
|
|
62
57
|
}
|
|
@@ -71,40 +66,59 @@ module.exports = class Push {
|
|
|
71
66
|
|
|
72
67
|
static async pushTgz(params, tgz, extensionName) {
|
|
73
68
|
const pushUrl = `${params.get('appUrl')}/-/cds/extensibility/push`;
|
|
74
|
-
const
|
|
69
|
+
const reqAuth = params.get('reqAuth') || {};
|
|
70
|
+
const headers = { 'content-type': 'application/json', ...reqAuth.headers };
|
|
75
71
|
const async = params.get('async');
|
|
76
72
|
if (async) {
|
|
77
|
-
|
|
73
|
+
headers['prefer'] = 'respond-async';
|
|
78
74
|
}
|
|
79
75
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
tag: extensionName
|
|
83
|
-
},
|
|
84
|
-
|
|
76
|
+
const response = await mtxFetch('POST', pushUrl, {
|
|
77
|
+
headers,
|
|
78
|
+
body: JSON.stringify({ extension: tgz, tag: extensionName })
|
|
79
|
+
}, params);
|
|
80
|
+
return response;
|
|
85
81
|
}
|
|
86
82
|
|
|
87
83
|
static async pollUntilFinished(jobUrl, params) {
|
|
88
|
-
|
|
89
|
-
|
|
84
|
+
const reqAuth = params.get('reqAuth') || {};
|
|
85
|
+
const headers = { ...reqAuth.headers };
|
|
90
86
|
|
|
87
|
+
return new Promise((resolve, reject) => {
|
|
91
88
|
const timeout = setTimeout(() => {
|
|
92
89
|
clearInterval(interval);
|
|
93
90
|
reject(new Error('cds push timed out after 5 minutes'));
|
|
94
91
|
}, 5 * 60 * 1000);
|
|
95
92
|
|
|
96
93
|
const interval = setInterval(async () => {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
94
|
+
try {
|
|
95
|
+
const response = await fetch(jobUrl, { headers });
|
|
96
|
+
if (!response.ok) {
|
|
97
|
+
clearInterval(interval);
|
|
98
|
+
clearTimeout(timeout);
|
|
99
|
+
reject(await buildResponseError('GET', jobUrl, response));
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const jobResult = await response.json();
|
|
103
|
+
const { status, tasks } = jobResult;
|
|
104
|
+
const error = tasks[0]?.error ?? 'cds push failed'
|
|
105
|
+
if (status === JOB_STATUS.FINISHED || status === JOB_STATUS.FAILED) {
|
|
106
|
+
clearInterval(interval);
|
|
107
|
+
clearTimeout(timeout);
|
|
108
|
+
|
|
109
|
+
if (status === JOB_STATUS.FINISHED) {
|
|
110
|
+
resolve();
|
|
111
|
+
} else if (status === JOB_STATUS.FAILED) {
|
|
112
|
+
reject(new Error(error));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
} catch (e) {
|
|
101
116
|
clearInterval(interval);
|
|
102
117
|
clearTimeout(timeout);
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
reject(new Error(error));
|
|
118
|
+
if (e.status) {
|
|
119
|
+
reject(e);
|
|
120
|
+
} else {
|
|
121
|
+
reject(buildNetworkError('GET', jobUrl, e));
|
|
108
122
|
}
|
|
109
123
|
}
|
|
110
124
|
}, 1000);
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const os = require('os');
|
|
3
3
|
const path = require('path');
|
|
4
|
-
const axios = require('axios');
|
|
5
|
-
|
|
6
4
|
const cds = require('../cds');
|
|
7
5
|
|
|
8
6
|
const { requireGlobal } = require('./util/dependencies');
|
|
9
7
|
const { getAppFromSuggestions, getSubdomain } = require('./util/cf');
|
|
10
8
|
const isBas = require('./util/env');
|
|
11
9
|
const { getMessage } = require('./util/logging');
|
|
10
|
+
const { buildResponseError, buildNetworkError, parseResponseBody } = require('./util/request');
|
|
12
11
|
const Question = require('./util/question');
|
|
13
12
|
const { httpSchema, httpsSchema, schemaRegex, localhostRegex } = require('./util/urls');
|
|
14
13
|
const { capitalize } = require('./util/strings');
|
|
@@ -111,20 +110,28 @@ async function getTokenUrl(params, renewUrl) {
|
|
|
111
110
|
}
|
|
112
111
|
|
|
113
112
|
async function fetchPasscodeUrl(params) {
|
|
114
|
-
|
|
115
|
-
DEBUG?.(`Trying to get passcode URL from GET`,
|
|
113
|
+
let fetchUrl = appUrlWith(OAUTH_META_PATH, params);
|
|
114
|
+
DEBUG?.(`Trying to get passcode URL from GET`, fetchUrl);
|
|
115
|
+
if (params.has('subdomain')) {
|
|
116
|
+
const u = new URL(fetchUrl);
|
|
117
|
+
u.searchParams.set('subdomain', params.get('subdomain'));
|
|
118
|
+
fetchUrl = u.toString();
|
|
119
|
+
}
|
|
116
120
|
let responseData;
|
|
117
121
|
try {
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
responseData = response
|
|
122
|
+
const response = await fetch(fetchUrl /* no auth required */);
|
|
123
|
+
if (!response.ok) {
|
|
124
|
+
throw await buildResponseError('GET', fetchUrl, response);
|
|
125
|
+
}
|
|
126
|
+
responseData = await parseResponseBody(response);
|
|
123
127
|
} catch (error) {
|
|
124
128
|
if (error.status === 404) {
|
|
125
129
|
DEBUG?.(`Request unsupported by server, ignoring (${error.message.replace(/ *Details:.*/s, '')})`);
|
|
126
|
-
} else {
|
|
130
|
+
} else if (error.status) {
|
|
127
131
|
DEBUG?.(getMessage(`Getting passcode URL failed`, { error }));
|
|
132
|
+
} else {
|
|
133
|
+
const netError = buildNetworkError('GET', fetchUrl, error);
|
|
134
|
+
DEBUG?.(getMessage(`Getting passcode URL failed`, { error: netError }));
|
|
128
135
|
}
|
|
129
136
|
return undefined;
|
|
130
137
|
}
|
package/lib/mtx/tenant_action.js
CHANGED
|
@@ -3,10 +3,9 @@ const { ParamCollection } = require('./params');
|
|
|
3
3
|
const AuthManager = require('./auth_manager');
|
|
4
4
|
const { localhostRegex } = require('./util/urls');
|
|
5
5
|
|
|
6
|
-
require('
|
|
6
|
+
const { mtxFetch } = require('./util/request');
|
|
7
7
|
const { getMessage } = require('./util/logging');
|
|
8
8
|
const { capitalize } = require('./util/strings');
|
|
9
|
-
const { handleHttpError } = require('./util/errors');
|
|
10
9
|
|
|
11
10
|
const DEBUG = cds.debug('cli');
|
|
12
11
|
|
|
@@ -63,16 +62,17 @@ module.exports = class TenantAction {
|
|
|
63
62
|
let parsedBody;
|
|
64
63
|
if (body) {
|
|
65
64
|
try {
|
|
66
|
-
parsedBody = JSON.parse(body)
|
|
65
|
+
parsedBody = JSON.parse(body);
|
|
67
66
|
} catch (error) {
|
|
68
67
|
throw getMessage('Invalid subscription body', { error, command: this.command });
|
|
69
68
|
}
|
|
70
69
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
70
|
+
const reqAuth = params.get('reqAuth') || {};
|
|
71
|
+
const headers = { 'content-type': 'application/json', ...reqAuth.headers };
|
|
72
|
+
await mtxFetch('POST', url, {
|
|
73
|
+
headers,
|
|
74
|
+
body: JSON.stringify({ tenant, metadata: parsedBody, options: parsedBody })
|
|
75
|
+
}, params);
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
async runWithServer(params, tenant) {
|
|
@@ -91,7 +91,7 @@ module.exports = class TenantAction {
|
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
const { cds_mtx } = require(libPath);
|
|
94
|
-
const body = params.get('body')
|
|
94
|
+
const body = params.get('body');
|
|
95
95
|
return cds_mtx(this.command, tenant, body);
|
|
96
96
|
}
|
|
97
97
|
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
const { inspect } = require('util');
|
|
2
|
+
|
|
3
|
+
const debug = require('../../cds').log('req');
|
|
4
|
+
|
|
5
|
+
function stacklessError(message) {
|
|
6
|
+
const { stackTraceLimit } = Error;
|
|
7
|
+
Error.stackTraceLimit = 0;
|
|
8
|
+
const error = new Error(message);
|
|
9
|
+
Error.stackTraceLimit = stackTraceLimit;
|
|
10
|
+
return error;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function extractDetails(data) {
|
|
14
|
+
if (!data) return undefined;
|
|
15
|
+
if (typeof data === 'string') {
|
|
16
|
+
if (/<html/i.test(data)) {
|
|
17
|
+
return '(HTML response received. If applicable, ensure the route to MTX is configured correctly in App Router.)';
|
|
18
|
+
}
|
|
19
|
+
if (data.length >= 1000) return undefined;
|
|
20
|
+
return data.trim();
|
|
21
|
+
}
|
|
22
|
+
if (typeof data === 'object') {
|
|
23
|
+
if (data.error_description) return data.error_description;
|
|
24
|
+
if (data.error && typeof data.error === 'object' && data.error.message) return data.error.message;
|
|
25
|
+
if (data.error && typeof data.error === 'string') return data.error;
|
|
26
|
+
if (typeof data.message === 'string') return data.message;
|
|
27
|
+
return inspect(data);
|
|
28
|
+
}
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function buildError(method, url, { status, statusText, data, errno, code, fetchError } = {}) {
|
|
33
|
+
const obfuscateUrl = url => url?.replace(/(passcode|refreshToken|clientsecret|key)=[^&]+/g, '$1=...');
|
|
34
|
+
const prefix = (url && method ? `${method.toUpperCase()} ${obfuscateUrl(url)}` : 'Request') + ' failed';
|
|
35
|
+
let error;
|
|
36
|
+
|
|
37
|
+
if (errno || code) {
|
|
38
|
+
const errInfo = [errno, code].filter(Boolean).join(' ');
|
|
39
|
+
const message = prefix +
|
|
40
|
+
`: ${errInfo}` +
|
|
41
|
+
(/\b(ENOTFOUND|EAI_AGAIN)\b/.test(code)
|
|
42
|
+
? `. Make sure the URL is correct and the server is running.`
|
|
43
|
+
: '');
|
|
44
|
+
error = stacklessError(message);
|
|
45
|
+
} else {
|
|
46
|
+
const details = extractDetails(data);
|
|
47
|
+
const reason = typeof data?.error === 'string' ? data.error : undefined;
|
|
48
|
+
const errObj = debug._debug && typeof data?.error === 'object'
|
|
49
|
+
? inspect(data.error)
|
|
50
|
+
: undefined;
|
|
51
|
+
|
|
52
|
+
const message = prefix +
|
|
53
|
+
(status || statusText || reason || details
|
|
54
|
+
? (status || statusText ? ':' : '') +
|
|
55
|
+
(status ? ` ${status}` : '') +
|
|
56
|
+
(statusText ? ` ${statusText}` : '') +
|
|
57
|
+
(reason ? `. ${reason}` : '') +
|
|
58
|
+
(details ? `. Details: ${details}` : '') +
|
|
59
|
+
(errObj ? `. Error object: ${errObj}` : '')
|
|
60
|
+
: '');
|
|
61
|
+
error = stacklessError(message);
|
|
62
|
+
if (status) error.status = status;
|
|
63
|
+
if (data?.passcode_url) {
|
|
64
|
+
error.auth = { passcode_url: data.passcode_url };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (debug._debug && fetchError) {
|
|
69
|
+
error.cause = fetchError;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return error;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function parseResponseBody(response) {
|
|
76
|
+
const contentType = response.headers.get('content-type') || '';
|
|
77
|
+
if (contentType.includes('application/json')) {
|
|
78
|
+
return response.json();
|
|
79
|
+
}
|
|
80
|
+
const text = await response.text();
|
|
81
|
+
try {
|
|
82
|
+
return JSON.parse(text);
|
|
83
|
+
} catch { /* ignore */ }
|
|
84
|
+
return text;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Build a fetch error from a non-ok response.
|
|
89
|
+
* Reads the response body and constructs a rich error with .status, .message, and .auth.
|
|
90
|
+
*
|
|
91
|
+
* @param {string} method - HTTP method
|
|
92
|
+
* @param {string} url - Request URL
|
|
93
|
+
* @param {Response} response - Fetch Response object
|
|
94
|
+
* @returns {Promise<Error>} error with .status, .message, .auth properties
|
|
95
|
+
*/
|
|
96
|
+
async function buildResponseError(method, url, response) {
|
|
97
|
+
let data;
|
|
98
|
+
try {
|
|
99
|
+
data = await parseResponseBody(response);
|
|
100
|
+
} catch { /* ignore */ }
|
|
101
|
+
return buildError(method, url, {
|
|
102
|
+
status: response.status,
|
|
103
|
+
statusText: response.statusText,
|
|
104
|
+
data,
|
|
105
|
+
fetchError: new Error(`${response.status} ${response.statusText}`)
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Build a fetch error from a network/system error.
|
|
111
|
+
*
|
|
112
|
+
* @param {string} method - HTTP method
|
|
113
|
+
* @param {string} url - Request URL
|
|
114
|
+
* @param {Error} fetchError - The caught error from fetch()
|
|
115
|
+
* @returns {Error} error with formatted message
|
|
116
|
+
*/
|
|
117
|
+
function buildNetworkError(method, url, fetchError) {
|
|
118
|
+
const cause = fetchError.cause ?? {};
|
|
119
|
+
const errno = fetchError.errno || cause.errno;
|
|
120
|
+
const code = fetchError.code || cause.code
|
|
121
|
+
|| fetchError.message?.match(/\b(ENOTFOUND|EAI_AGAIN|ECONNREFUSED|ECONNRESET|ETIMEDOUT|EHOSTUNREACH)\b/)?.[1]
|
|
122
|
+
|| cause.message?.match(/\b(ENOTFOUND|EAI_AGAIN|ECONNREFUSED|ECONNRESET|ETIMEDOUT|EHOSTUNREACH)\b/)?.[1];
|
|
123
|
+
return buildError(method, url, { errno, code, fetchError });
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const { handleHttpError } = require('./errors');
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Fetch with standardized error handling for mtx modules.
|
|
130
|
+
* Network errors are wrapped via buildNetworkError.
|
|
131
|
+
* HTTP errors are passed through handleHttpError which maps status codes to user-facing messages.
|
|
132
|
+
*
|
|
133
|
+
* @param {string} method - HTTP method
|
|
134
|
+
* @param {string} url - Request URL
|
|
135
|
+
* @param {object} [options] - fetch options (headers, body, etc.)
|
|
136
|
+
* @param {object} [params] - ParamCollection for handleHttpError context
|
|
137
|
+
* @returns {Promise<Response>}
|
|
138
|
+
*/
|
|
139
|
+
async function mtxFetch(method, url, options = {}, params) {
|
|
140
|
+
let response;
|
|
141
|
+
try {
|
|
142
|
+
response = await fetch(url, { method, ...options });
|
|
143
|
+
} catch (e) {
|
|
144
|
+
throw buildNetworkError(method, url, e);
|
|
145
|
+
}
|
|
146
|
+
if (!response.ok) {
|
|
147
|
+
handleHttpError(await buildResponseError(method, url, response), params, { url });
|
|
148
|
+
}
|
|
149
|
+
return response;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
module.exports = {
|
|
153
|
+
buildError,
|
|
154
|
+
buildResponseError,
|
|
155
|
+
buildNetworkError,
|
|
156
|
+
parseResponseBody,
|
|
157
|
+
mtxFetch
|
|
158
|
+
};
|
package/lib/util/cf.js
CHANGED
|
@@ -4,8 +4,6 @@ const os = require('os');
|
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const util = require('util');
|
|
6
6
|
|
|
7
|
-
const axios = require('axios');
|
|
8
|
-
|
|
9
7
|
const IS_WIN = os.platform() === 'win32';
|
|
10
8
|
const execAsync = util.promisify(cp.exec);
|
|
11
9
|
|
|
@@ -29,7 +27,6 @@ const OPERATION_STATE_FAILED = 'failed';
|
|
|
29
27
|
const OPERATION_STATE_SUCCEEDED = 'succeeded';
|
|
30
28
|
|
|
31
29
|
const CF_CLIENT_MINIMUM_VERSION = 8;
|
|
32
|
-
const ENCODED_COMMA = encodeURIComponent(',');
|
|
33
30
|
|
|
34
31
|
module.exports = new class CfUtil {
|
|
35
32
|
|
|
@@ -68,42 +65,46 @@ module.exports = new class CfUtil {
|
|
|
68
65
|
}
|
|
69
66
|
|
|
70
67
|
async curl(urlPath, queryObj, bodyObj) {
|
|
71
|
-
|
|
68
|
+
let url = this.#target.apiEndpoint + urlPath
|
|
72
69
|
|
|
73
|
-
const
|
|
74
|
-
headers: { 'Authorization': this.#target.token }
|
|
75
|
-
};
|
|
70
|
+
const headers = { 'Authorization': this.#target.token }
|
|
76
71
|
|
|
77
72
|
if (queryObj && Object.keys(queryObj).length > 0) {
|
|
78
|
-
const
|
|
73
|
+
const parts = []
|
|
79
74
|
for (const [k, v] of Object.entries(queryObj)) {
|
|
80
|
-
|
|
81
|
-
params[k] = v.replaceAll(',', ENCODED_COMMA);
|
|
75
|
+
parts.push(`${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
|
|
82
76
|
}
|
|
83
|
-
|
|
77
|
+
url += '?' + parts.join('&')
|
|
84
78
|
}
|
|
85
79
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
if (LOG._debug) {
|
|
94
|
-
options.headers.Authorization = 'bearer <token>';
|
|
95
|
-
LOG.debug(`POST ${url}\n${JSON.stringify(options, null, 4)}\n${JSON.stringify(response.data, null, 4)}`);
|
|
96
|
-
}
|
|
80
|
+
const fetchOptions = { headers }
|
|
81
|
+
if (bodyObj) {
|
|
82
|
+
fetchOptions.method = 'POST'
|
|
83
|
+
fetchOptions.headers['Content-Type'] = 'application/json'
|
|
84
|
+
fetchOptions.body = JSON.stringify(bodyObj)
|
|
85
|
+
}
|
|
97
86
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
87
|
+
const response = await fetch(url, fetchOptions)
|
|
88
|
+
|
|
89
|
+
if (!response.ok) {
|
|
90
|
+
const text = await response.text()
|
|
91
|
+
let data
|
|
92
|
+
try { data = JSON.parse(text) } catch { /* ignore */ }
|
|
93
|
+
if (data?.errors?.[0]) {
|
|
94
|
+
throw JSON.stringify(data.errors[0], null, 2)
|
|
105
95
|
}
|
|
96
|
+
throw `${response.status} ${response.statusText}: while accessing ${url}`
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const text = await response.text()
|
|
100
|
+
const data = text ? JSON.parse(text) : {}
|
|
101
|
+
|
|
102
|
+
if (LOG._debug) {
|
|
103
|
+
const method = fetchOptions.method || 'GET'
|
|
104
|
+
LOG.debug(`${method} ${url}\n${JSON.stringify({ headers: { ...headers, Authorization: 'bearer <token>' } }, null, 4)}\n${JSON.stringify(data, null, 4)}`)
|
|
106
105
|
}
|
|
106
|
+
|
|
107
|
+
return data
|
|
107
108
|
}
|
|
108
109
|
|
|
109
110
|
_extract(string, pattern, errorMsg) {
|
package/lib/watch/node-watch.js
CHANGED
|
@@ -31,7 +31,7 @@ module.exports = ({ cwd = process.cwd(), script, ext, ignore=RegExp(), watched,
|
|
|
31
31
|
|
|
32
32
|
const updated = eventsAndOpts.filter(evt => evt.type && evt.type === 'update').map(evt => evt.name)
|
|
33
33
|
emitter.emit ('restart', updated)
|
|
34
|
-
env.CDS_WATCH_HOT_RELOAD = options['experimental-hot-reload']
|
|
34
|
+
// env.CDS_WATCH_HOT_RELOAD = options['experimental-hot-reload']
|
|
35
35
|
child = fork (script, { cwd, env, stdio:'inherit', execArgv }, (err)=>{
|
|
36
36
|
if (err) console.error (err)
|
|
37
37
|
})
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
const cds = require('../../lib/cds')
|
|
2
|
+
const { pathToFileURL } = require('node:url')
|
|
3
|
+
const { path, stat, readdir, isdir, colors: { BOLD, RESET } } = cds.utils
|
|
4
|
+
|
|
5
|
+
let hmrPort = 24100
|
|
6
|
+
|
|
7
|
+
async function hasViteConfig(appPath) {
|
|
8
|
+
for (const name of ['vite.config.js', 'vite.config.mjs', 'vite.config.ts']) {
|
|
9
|
+
const file = await stat(path.join(appPath, name)).catch(e => e.code === 'ENOENT' ? null : Promise.reject(e))
|
|
10
|
+
return !!file
|
|
11
|
+
}
|
|
12
|
+
return false
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function findViteApps() {
|
|
16
|
+
const app = cds.env.folders.app
|
|
17
|
+
const apps = []
|
|
18
|
+
if (!isdir(app)) return apps
|
|
19
|
+
const entries = await readdir(app)
|
|
20
|
+
for (const entry of entries) {
|
|
21
|
+
const appName = typeof entry === 'string' ? entry : entry.name
|
|
22
|
+
if (!appName) continue
|
|
23
|
+
const appPath = path.join(cds.root, app, appName)
|
|
24
|
+
if (!isdir(appPath)) continue
|
|
25
|
+
if (await hasViteConfig(appPath)) apps.push({ name: appName, path: appPath })
|
|
26
|
+
}
|
|
27
|
+
return apps
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function loadCreateServer(fromPath) {
|
|
31
|
+
try {
|
|
32
|
+
const vitePath = require.resolve('vite', { paths: [fromPath] })
|
|
33
|
+
return (await import(pathToFileURL(vitePath).href)).createServer
|
|
34
|
+
} catch(e) {
|
|
35
|
+
if (e.code !== 'MODULE_NOT_FOUND') throw e
|
|
36
|
+
/* try next path */
|
|
37
|
+
}
|
|
38
|
+
const relative = path.relative(cds.root, fromPath)
|
|
39
|
+
throw `[cds] - Found Vite app in ${relative}, but package "vite" is not installed. Run ${BOLD}npm install vite --prefix ${relative}${RESET} to install it.`
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
cds.on('bootstrap', app => {
|
|
43
|
+
|
|
44
|
+
let viteMiddleware = null
|
|
45
|
+
app.use((req, res, next) => {
|
|
46
|
+
if (viteMiddleware) return viteMiddleware(req, res, next)
|
|
47
|
+
next()
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
cds.on('served', async () => {
|
|
51
|
+
const viteApps = await findViteApps()
|
|
52
|
+
if (!viteApps.length) return
|
|
53
|
+
|
|
54
|
+
const createServer = await loadCreateServer(viteApps[0].path)
|
|
55
|
+
|
|
56
|
+
// Build a middleware chain from all Vite servers, keyed by base path
|
|
57
|
+
const viteServers = []
|
|
58
|
+
const normalizeBase = base => {
|
|
59
|
+
if (!base.startsWith('/')) base = `/${base}`
|
|
60
|
+
return base.endsWith('/') ? base : `${base}/`
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
for (const viteApp of viteApps) {
|
|
64
|
+
const base = normalizeBase(viteApp.name)
|
|
65
|
+
const server = await createServer({ base, root: viteApp.path, server: { middlewareMode: true, hmr: { port: hmrPort++ } }})
|
|
66
|
+
viteServers.push({ base, middlewares: server.middlewares })
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
viteMiddleware = (req, res, next) => {
|
|
70
|
+
const url = req.url || ''
|
|
71
|
+
const match = viteServers.find(v => url.startsWith(v.base) || url.startsWith('/@'))
|
|
72
|
+
if (match) return match.middlewares(req, res, next)
|
|
73
|
+
next()
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
})
|
package/lib/watch/watched.js
CHANGED
|
@@ -8,14 +8,15 @@
|
|
|
8
8
|
// run through cli in order to use its error handling
|
|
9
9
|
const args = JSON.parse(process.env._args)
|
|
10
10
|
const cds = require('../cds'); cds.watched = true
|
|
11
|
+
require('./vite-watch')
|
|
11
12
|
const bootstrap = require('../../bin/cds')(...args)
|
|
12
13
|
|
|
13
14
|
cds.once('listening', async ({ server }) => {
|
|
14
15
|
process.send({ code: 'listening', port: server.address().port })
|
|
15
|
-
if (process.env.CDS_WATCH_HOT_RELOAD
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
16
|
+
// if (process.env.CDS_WATCH_HOT_RELOAD) {
|
|
17
|
+
// const hotReload = require('./hot-reload');
|
|
18
|
+
// await hotReload()
|
|
19
|
+
// }
|
|
19
20
|
})
|
|
20
21
|
|
|
21
22
|
try {
|