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.
Files changed (2) hide show
  1. package/cds-plugin.js +107 -3
  2. 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 = process.env.BTP_COPILOT_URL,
13
- appId = process.env.BTP_COPILOT_APP_ID,
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 = (...args) => console.log('\x1b[36m[btp-copilot]\x1b[0m', ...args);
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.0",
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"],