@weave-apps/sdk 0.6.0 → 0.8.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/bin/build.js CHANGED
@@ -3,7 +3,13 @@
3
3
  /**
4
4
  * Weave App Builder
5
5
  *
6
- * Transpiles TypeScript to clean JavaScript for Weave Platform
6
+ * Post-processes the esbuild IIFE bundle for the Weave Platform.
7
+ * Adds a header comment and removes the "use strict" directive.
8
+ *
9
+ * The esbuild IIFE format wraps all code in (() => { ... })() so
10
+ * helper functions and variables stay private to the closure.
11
+ * SDK globals (WeaveBaseApp, weaveDOM, weaveAPI) are resolved
12
+ * inside the closure by the esbuild plugin in compile.js.
7
13
  */
8
14
 
9
15
  const fs = require('fs');
@@ -29,31 +35,17 @@ const distFile = path.join(appDir, 'dist', `${appName}.js`);
29
35
  // Check if dist file exists
30
36
  if (!fs.existsSync(distFile)) {
31
37
  console.error(`❌ Error: dist/${appName}.js not found`);
32
- console.error('Run "tsc" first to compile TypeScript');
38
+ console.error('Run "weave-compile" first to compile TypeScript');
33
39
  process.exit(1);
34
40
  }
35
41
 
36
42
  console.log('🔨 Building Weave app...\n');
37
43
 
38
- // Read compiled TypeScript output
44
+ // Read esbuild IIFE output
39
45
  let code = fs.readFileSync(distFile, 'utf8');
40
46
 
41
- // Remove import statements (SDK is globally available)
42
- code = code.replace(/^import .* from .*$/gm, '');
43
- code = code.replace(/^import .*$/gm, '');
44
-
45
- // Remove export statements
46
- code = code.replace(/^export \{[^}]*\};?$/gm, '');
47
- code = code.replace(/^export /gm, '');
48
-
49
- // Replace WeaveBaseApp with window.WeaveBaseApp
50
- code = code.replace(/extends WeaveBaseApp/g, 'extends window.WeaveBaseApp');
51
-
52
- // Replace weaveDOM with window.weaveDOM (but not if already prefixed with window. or this.)
53
- code = code.replace(/(?<!window\.)(?<!this\.)weaveDOM\./g, 'window.weaveDOM.');
54
-
55
- // Replace weaveAPI with window.weaveAPI (but not if already prefixed with window. or this.)
56
- code = code.replace(/(?<!window\.)(?<!this\.)weaveAPI\./g, 'window.weaveAPI.');
47
+ // Remove "use strict" directive (esbuild adds it, not needed in IIFE)
48
+ code = code.replace(/^"use strict";\n/gm, '');
57
49
 
58
50
  // Remove empty lines created by removals
59
51
  code = code.replace(/\n\n\n+/g, '\n\n');
@@ -65,10 +57,6 @@ const header = `/**
65
57
  * Built with Weave App SDK
66
58
  * Generated: ${new Date().toISOString()}
67
59
  */
68
-
69
- // Access SDK from window globals
70
- const { WeaveBaseApp, weaveDOM } = window;
71
-
72
60
  `;
73
61
 
74
62
  code = header + code;
package/bin/compile.js CHANGED
@@ -77,28 +77,7 @@ if (!fs.existsSync(tsconfigPath)) {
77
77
  process.exit(1);
78
78
  }
79
79
 
80
- // Look for esbuild in multiple locations
81
- let esbuildPath = null;
82
- const possibleEsbuildPaths = [
83
- path.join(appDir, 'node_modules', '.bin', 'esbuild'),
84
- path.join(sdkDir, 'node_modules', '.bin', 'esbuild'),
85
- ];
86
-
87
- for (const candidate of possibleEsbuildPaths) {
88
- if (fs.existsSync(candidate)) {
89
- esbuildPath = candidate;
90
- break;
91
- }
92
- }
93
-
94
- if (!esbuildPath) {
95
- console.error('❌ Error: esbuild not found');
96
- console.error('It should be installed as part of @weave-apps/sdk.');
97
- console.error('Try running: npm install');
98
- process.exit(1);
99
- }
100
-
101
- try {
80
+ async function compile() {
102
81
  // Create dist directory if it doesn't exist
103
82
  const distDir = path.join(appDir, 'dist');
104
83
  if (!fs.existsSync(distDir)) {
@@ -109,7 +88,7 @@ try {
109
88
  const targetFile = path.join(distDir, `${appName}.js`);
110
89
 
111
90
  // Step 1: Type-check with tsc (no emit)
112
- console.log('īŋŊ Type-checking with TypeScript...');
91
+ console.log('🔍 Type-checking with TypeScript...');
113
92
  const tscCommand = tscPath === 'npx tsc'
114
93
  ? `npx tsc --project ${tsconfigPath} --noEmit`
115
94
  : `"${tscPath}" --project ${tsconfigPath} --noEmit`;
@@ -122,25 +101,49 @@ try {
122
101
  console.log('✅ Type-check passed\n');
123
102
 
124
103
  // Step 2: Bundle with esbuild (all imports resolved into single file)
104
+ // Uses IIFE format so all code is wrapped in a closure — no global namespace pollution.
105
+ // Helper functions, utilities, etc. stay private inside the closure.
106
+ // Only customElements.define() escapes to register the web component.
125
107
  console.log('đŸ“Ļ Bundling with esbuild...');
126
- const esbuildCommand = [
127
- `"${esbuildPath}"`,
128
- `"${entryPoint}"`,
129
- '--bundle',
130
- `--outfile="${targetFile}"`,
131
- '--format=esm',
132
- '--target=es2020',
133
- '--platform=browser',
134
- // Mark SDK as external — build.js will replace with window globals
135
- '--external:@weave-apps/sdk',
136
- '--external:@weave/app-sdk',
137
- ].join(' ');
138
-
139
- execSync(esbuildCommand, {
140
- stdio: 'inherit',
141
- cwd: appDir
108
+ const esbuild = require('esbuild');
109
+
110
+ // Plugin to resolve SDK imports to window globals instead of trying to find the package
111
+ const weaveSDKPlugin = {
112
+ name: 'weave-sdk-globals',
113
+ setup(build) {
114
+ // Intercept imports of the SDK packages
115
+ build.onResolve({ filter: /^@weave-apps\/sdk$|^@weave\/app-sdk$/ }, (args) => {
116
+ return { path: args.path, namespace: 'weave-sdk' };
117
+ });
118
+ // Return a virtual module that re-exports from window globals
119
+ build.onLoad({ filter: /.*/, namespace: 'weave-sdk' }, () => {
120
+ return {
121
+ contents: `
122
+ export const WeaveBaseApp = window.WeaveBaseApp;
123
+ export const weaveDOM = window.weaveDOM;
124
+ export const weaveAPI = window.weaveAPI;
125
+ `,
126
+ loader: 'js',
127
+ };
128
+ });
129
+ },
130
+ };
131
+
132
+ const result = await esbuild.build({
133
+ entryPoints: [entryPoint],
134
+ bundle: true,
135
+ outfile: targetFile,
136
+ format: 'iife',
137
+ target: 'es2020',
138
+ platform: 'browser',
139
+ plugins: [weaveSDKPlugin],
142
140
  });
143
141
 
142
+ if (result.errors.length > 0) {
143
+ console.error('❌ esbuild errors:', result.errors);
144
+ process.exit(1);
145
+ }
146
+
144
147
  console.log('✅ Bundle complete\n');
145
148
 
146
149
  // Step 3: Extract settings schema and inject into compiled JS
@@ -160,15 +163,16 @@ try {
160
163
 
161
164
  console.log('');
162
165
 
163
- // Step 4: Run weave-build to transpile (strip imports, add window globals)
166
+ // Step 4: Run weave-build to add header comment
164
167
  console.log('🔧 Transpiling to Weave format...');
165
168
  const buildScript = path.join(__dirname, 'build.js');
166
169
  execSync(`node "${buildScript}"`, {
167
170
  stdio: 'inherit',
168
171
  cwd: appDir
169
172
  });
173
+ }
170
174
 
171
- } catch (error) {
175
+ compile().catch((error) => {
172
176
  console.error('\n❌ Build failed');
173
177
  process.exit(1);
174
- }
178
+ });
@@ -0,0 +1,104 @@
1
+ const { describe, it } = require('node:test');
2
+ const assert = require('node:assert/strict');
3
+ const fs = require('fs');
4
+ const os = require('os');
5
+ const path = require('path');
6
+ const { execFileSync } = require('child_process');
7
+
8
+ const scriptPath = path.resolve(__dirname, '../scripts/extract-settings-schema.js');
9
+
10
+ function createTempFiles(jsContent) {
11
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'weave-extract-settings-'));
12
+ const tsPath = path.join(tmpDir, 'app.ts');
13
+ const jsPath = path.join(tmpDir, 'bundle.js');
14
+
15
+ const tsContent = `
16
+ import { WeaveBaseApp } from '@weave-apps/sdk';
17
+
18
+ interface AppSettings {
19
+ /** @description Example domain */
20
+ /** @default "https://example.com" */
21
+ DCP_DOMAIN?: string;
22
+ }
23
+
24
+ class DemoApp extends WeaveBaseApp<AppSettings, Record<string, never>> {
25
+ constructor() {
26
+ super({ id: 'demo', name: 'Demo', version: '1.0.0' });
27
+ }
28
+ }
29
+ `;
30
+
31
+ fs.writeFileSync(tsPath, tsContent, 'utf8');
32
+ fs.writeFileSync(jsPath, jsContent, 'utf8');
33
+
34
+ return { tmpDir, tsPath, jsPath };
35
+ }
36
+
37
+ function runScript(tsPath, jsPath) {
38
+ execFileSync('node', [scriptPath, tsPath, jsPath], { stdio: 'pipe' });
39
+ }
40
+
41
+ describe('extract-settings-schema script', () => {
42
+ it('injects schema for class declarations extending window.WeaveBaseApp', () => {
43
+ const { tmpDir, tsPath, jsPath } = createTempFiles(
44
+ `class DemoApp extends window.WeaveBaseApp { constructor() {} }`
45
+ );
46
+
47
+ try {
48
+ runScript(tsPath, jsPath);
49
+ const output = fs.readFileSync(jsPath, 'utf8');
50
+ assert.match(output, /@weave-settings-schema/);
51
+ assert.match(output, /class DemoApp extends window\.WeaveBaseApp/);
52
+ assert.match(output, /"key": "DCP_DOMAIN"/);
53
+ } finally {
54
+ fs.rmSync(tmpDir, { recursive: true, force: true });
55
+ }
56
+ });
57
+
58
+ it('injects schema for assigned anonymous classes in bundled output', () => {
59
+ const { tmpDir, tsPath, jsPath } = createTempFiles(
60
+ `var DemoApp = class extends window.WeaveBaseApp { constructor() {} };`
61
+ );
62
+
63
+ try {
64
+ runScript(tsPath, jsPath);
65
+ const output = fs.readFileSync(jsPath, 'utf8');
66
+ assert.match(output, /@weave-settings-schema/);
67
+ assert.match(output, /var DemoApp = class extends window\.WeaveBaseApp/);
68
+ assert.match(output, /"defaultValue": "https:\/\/example.com"/);
69
+ } finally {
70
+ fs.rmSync(tmpDir, { recursive: true, force: true });
71
+ }
72
+ });
73
+
74
+ it('does not inject twice when schema already exists', () => {
75
+ const { tmpDir, tsPath, jsPath } = createTempFiles(
76
+ `/**\n * @weave-settings-schema []\n */\nvar DemoApp = class extends window.WeaveBaseApp { constructor() {} };`
77
+ );
78
+
79
+ try {
80
+ runScript(tsPath, jsPath);
81
+ const output = fs.readFileSync(jsPath, 'utf8');
82
+ const schemaOccurrences = (output.match(/@weave-settings-schema/g) || []).length;
83
+ assert.equal(schemaOccurrences, 1);
84
+ } finally {
85
+ fs.rmSync(tmpDir, { recursive: true, force: true });
86
+ }
87
+ });
88
+
89
+ it('injects schema when compiling the real heidi-to-dcp mock app with local SDK scripts', () => {
90
+ const mockAppDir = path.resolve(__dirname, '../mocks/heidi-to-dcp');
91
+ const compileScriptPath = path.resolve(__dirname, 'compile.js');
92
+ const outputPath = path.join(mockAppDir, 'dist', 'demo-weave-heidi-dcp.js');
93
+
94
+ execFileSync('node', [compileScriptPath], {
95
+ cwd: mockAppDir,
96
+ stdio: 'pipe',
97
+ });
98
+
99
+ const output = fs.readFileSync(outputPath, 'utf8');
100
+ assert.match(output, /@weave-settings-schema/);
101
+ assert.match(output, /"key": "DCP_DOMAIN"/);
102
+ assert.match(output, /"key": "HEIDI_CLIENT_NAME_SELECTOR"/);
103
+ });
104
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@weave-apps/sdk",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "description": "SDK for building Weave Micro Apps",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -173,14 +173,34 @@ function injectSchemaIntoJS(jsFilePath, schema) {
173
173
 
174
174
  let jsCode = fs.readFileSync(jsFilePath, 'utf-8');
175
175
 
176
- // Find the class definition
177
- const classMatch = jsCode.match(/class\s+(\w+)\s+extends\s+WeaveBaseApp/);
178
- if (!classMatch) {
179
- console.warn('âš ī¸ Could not find class extending WeaveBaseApp');
176
+ if (jsCode.includes('@weave-settings-schema')) {
177
+ console.log('â„šī¸ Settings schema already present, skipping injection');
180
178
  return;
181
179
  }
182
180
 
183
- const className = classMatch[1];
181
+ // Find app class definitions in both source-like and bundled outputs.
182
+ // Examples:
183
+ // class MyApp extends WeaveBaseApp
184
+ // class MyApp extends window.WeaveBaseApp
185
+ // var MyApp = class extends window.WeaveBaseApp
186
+ const classDefinitionPatterns = [
187
+ /(?:var|let|const)\s+\w+\s*=\s*class(?:\s+\w+)?\s+extends\s+(?:window\.)?WeaveBaseApp\b/,
188
+ /class\s+\w+\s+extends\s+(?:window\.)?WeaveBaseApp\b/,
189
+ ];
190
+
191
+ let classDefinition = null;
192
+ for (const pattern of classDefinitionPatterns) {
193
+ const match = jsCode.match(pattern);
194
+ if (match) {
195
+ classDefinition = match[0];
196
+ break;
197
+ }
198
+ }
199
+
200
+ if (!classDefinition) {
201
+ console.warn('âš ī¸ Could not find class extending WeaveBaseApp');
202
+ return;
203
+ }
184
204
 
185
205
  // Create JSDoc comment with schema
186
206
  const schemaComment = `
@@ -189,10 +209,8 @@ function injectSchemaIntoJS(jsFilePath, schema) {
189
209
  */`;
190
210
 
191
211
  // Inject before class definition
192
- jsCode = jsCode.replace(
193
- new RegExp(`(class\\s+${className}\\s+extends\\s+WeaveBaseApp)`, 'g'),
194
- `${schemaComment}\n$1`
195
- );
212
+ const classDefinitionIndex = jsCode.indexOf(classDefinition);
213
+ jsCode = `${jsCode.slice(0, classDefinitionIndex)}${schemaComment}\n${jsCode.slice(classDefinitionIndex)}`;
196
214
 
197
215
  fs.writeFileSync(jsFilePath, jsCode, 'utf-8');
198
216
  console.log(`✅ Injected settings schema into ${jsFilePath}`);