cap-copilot-sdk 0.1.0 → 0.1.1
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 +107 -3
- package/package.json +1 -1
package/cds-plugin.js
CHANGED
|
@@ -3,31 +3,135 @@
|
|
|
3
3
|
// singleton instance that cds watch uses — not a duplicate from this package's location.
|
|
4
4
|
const cdsPath = require.resolve('@sap/cds', { paths: [process.cwd()] });
|
|
5
5
|
const cds = require(cdsPath);
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const fs = require('fs');
|
|
6
8
|
const { extract, extractFromServices } = require('./src/SchemaExtractor');
|
|
7
9
|
const { scan } = require('./src/ProjectScanner');
|
|
8
10
|
const { register } = require('./src/Registrar');
|
|
9
11
|
|
|
10
12
|
const cfg = cds.env.requires?.['btp-copilot'] ?? {};
|
|
11
13
|
const {
|
|
12
|
-
backendUrl
|
|
13
|
-
appId
|
|
14
|
+
backendUrl = process.env.BTP_COPILOT_URL,
|
|
15
|
+
appId = process.env.BTP_COPILOT_APP_ID,
|
|
14
16
|
appName,
|
|
15
17
|
serviceUrl,
|
|
18
|
+
iframeUrl = process.env.BTP_COPILOT_IFRAME_URL,
|
|
16
19
|
token = process.env.BTP_COPILOT_TOKEN,
|
|
17
20
|
includeProjectFiles = true,
|
|
18
21
|
includeHandlers = true,
|
|
19
22
|
includeViews = true,
|
|
23
|
+
injectWidget = true, // set false to disable auto-injection
|
|
24
|
+
widgetPosition = 'bottom-right',
|
|
20
25
|
} = cfg;
|
|
21
26
|
|
|
22
|
-
const log
|
|
27
|
+
const log = (...args) => console.log('\x1b[36m[btp-copilot]\x1b[0m', ...args);
|
|
23
28
|
const warn = (...args) => console.warn('\x1b[33m[btp-copilot]\x1b[0m', ...args);
|
|
24
29
|
const err = (...args) => console.error('\x1b[31m[btp-copilot]\x1b[0m', ...args);
|
|
25
30
|
|
|
31
|
+
// ── Widget auto-injection middleware ─────────────────────────────────────────
|
|
32
|
+
// Intercepts every HTML response from CAP and appends the <btp-copilot> tag
|
|
33
|
+
// + the widget script. Works for ALL Fiori/UI5 apps served by this CAP server
|
|
34
|
+
// without any manual changes to their index.html files.
|
|
35
|
+
function _buildWidgetSnippet () {
|
|
36
|
+
// Serve the widget JS bundle from node_modules so no CDN dependency
|
|
37
|
+
const widgetBundlePath = (() => {
|
|
38
|
+
try {
|
|
39
|
+
return require.resolve('cap-copilot-widget/dist/btp-copilot.js',
|
|
40
|
+
{ paths: [process.cwd()] });
|
|
41
|
+
} catch { return null; }
|
|
42
|
+
})();
|
|
43
|
+
|
|
44
|
+
const scriptTag = widgetBundlePath
|
|
45
|
+
? `<script src="/__btp-copilot-widget/btp-copilot.js"></script>`
|
|
46
|
+
: `<script src="https://unpkg.com/cap-copilot-widget/dist/btp-copilot.js"></script>`;
|
|
47
|
+
|
|
48
|
+
const attrs = [
|
|
49
|
+
`app-id="${appId}"`,
|
|
50
|
+
`position="${widgetPosition}"`,
|
|
51
|
+
iframeUrl ? `iframe-url="${iframeUrl}"` : '',
|
|
52
|
+
serviceUrl ? `service-url="${serviceUrl}"` : '',
|
|
53
|
+
appName ? `app-name="${appName}"` : '',
|
|
54
|
+
].filter(Boolean).join(' ');
|
|
55
|
+
|
|
56
|
+
return { snippet: `\n${scriptTag}\n<btp-copilot ${attrs}></btp-copilot>\n</body>`, widgetBundlePath };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function _registerWidgetMiddleware (app) {
|
|
60
|
+
if (!injectWidget || !iframeUrl) {
|
|
61
|
+
if (!iframeUrl) warn('No iframeUrl configured — widget auto-injection skipped. Add "iframeUrl" to cds.requires["btp-copilot"].');
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const { snippet, widgetBundlePath } = _buildWidgetSnippet();
|
|
66
|
+
|
|
67
|
+
// Serve the local widget bundle at a fixed path so no CDN needed
|
|
68
|
+
if (widgetBundlePath) {
|
|
69
|
+
app.get('/__btp-copilot-widget/btp-copilot.js', (_req, res) => {
|
|
70
|
+
res.setHeader('Content-Type', 'application/javascript');
|
|
71
|
+
res.setHeader('Cache-Control', 'public, max-age=3600');
|
|
72
|
+
res.sendFile(widgetBundlePath);
|
|
73
|
+
});
|
|
74
|
+
log('Serving widget bundle at /__btp-copilot-widget/btp-copilot.js');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Intercept HTML responses and inject the widget before </body>
|
|
78
|
+
app.use((req, res, next) => {
|
|
79
|
+
const _write = res.write.bind(res);
|
|
80
|
+
const _end = res.end.bind(res);
|
|
81
|
+
let _buffer = '';
|
|
82
|
+
let _isHtml = false;
|
|
83
|
+
let _intercepted = false;
|
|
84
|
+
|
|
85
|
+
res.on('pipe', () => {});
|
|
86
|
+
|
|
87
|
+
const _intercept = () => {
|
|
88
|
+
const ct = res.getHeader('content-type') || '';
|
|
89
|
+
_isHtml = ct.includes('text/html');
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const _inject = (chunk) => {
|
|
93
|
+
if (!_isHtml) return chunk;
|
|
94
|
+
const str = Buffer.isBuffer(chunk) ? chunk.toString('utf8') : (chunk || '');
|
|
95
|
+
if (str.includes('</body>')) {
|
|
96
|
+
_intercepted = true;
|
|
97
|
+
return str.replace('</body>', snippet);
|
|
98
|
+
}
|
|
99
|
+
return str;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// Wrap write
|
|
103
|
+
res.write = function (chunk, encoding, callback) {
|
|
104
|
+
_intercept();
|
|
105
|
+
const patched = _inject(chunk);
|
|
106
|
+
return _write(patched, encoding, callback);
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// Wrap end
|
|
110
|
+
res.end = function (chunk, encoding, callback) {
|
|
111
|
+
_intercept();
|
|
112
|
+
if (chunk) {
|
|
113
|
+
const patched = _inject(chunk);
|
|
114
|
+
return _end(patched, encoding, callback);
|
|
115
|
+
}
|
|
116
|
+
return _end(chunk, encoding, callback);
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
next();
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
log(`Widget auto-injection active — <btp-copilot> will appear in all HTML pages.`);
|
|
123
|
+
}
|
|
124
|
+
|
|
26
125
|
if (!backendUrl || !appId) {
|
|
27
126
|
log('No backendUrl/appId in cds.requires["btp-copilot"] — skipping registration.');
|
|
28
127
|
} else if (!/^[a-zA-Z0-9_-]+$/.test(appId)) {
|
|
29
128
|
warn(`appId "${appId}" contains invalid characters. Use only letters, digits, _ and -.`);
|
|
30
129
|
} else {
|
|
130
|
+
// Register the middleware as early as possible so it wraps all HTML routes
|
|
131
|
+
cds.on('bootstrap', (app) => {
|
|
132
|
+
_registerWidgetMiddleware(app);
|
|
133
|
+
});
|
|
134
|
+
|
|
31
135
|
cds.on('served', async (services) => {
|
|
32
136
|
try {
|
|
33
137
|
log(`Collecting context for app "${appId}"…`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cap-copilot-sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "CDS plugin: auto-extracts entity schemas, relationships, project structure and registers with BTP Copilot backend",
|
|
5
5
|
"main": "cds-plugin.js",
|
|
6
6
|
"files": ["src", "cds-plugin.js"],
|