@weave-apps/sdk 0.5.0 → 0.7.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
@@ -3,7 +3,10 @@
3
3
  /**
4
4
  * Weave App Compiler
5
5
  *
6
- * Compiles TypeScript and transpiles to Weave-ready JavaScript
6
+ * Compiles TypeScript and transpiles to Weave-ready JavaScript.
7
+ * Supports multi-file apps by bundling all imports into a single output file.
8
+ *
9
+ * Uses esbuild for bundling and tsc for type-checking only.
7
10
  */
8
11
 
9
12
  const { execSync } = require('child_process');
@@ -74,42 +77,82 @@ if (!fs.existsSync(tsconfigPath)) {
74
77
  process.exit(1);
75
78
  }
76
79
 
77
- try {
80
+ async function compile() {
78
81
  // Create dist directory if it doesn't exist
79
82
  const distDir = path.join(appDir, 'dist');
80
83
  if (!fs.existsSync(distDir)) {
81
84
  fs.mkdirSync(distDir, { recursive: true });
82
85
  }
83
86
 
84
- // Step 1: Compile TypeScript using SDK's tsconfig and TypeScript
85
- console.log('đŸ“Ļ Compiling TypeScript...');
87
+ const entryPoint = path.join(appDir, 'src', 'app.ts');
88
+ const targetFile = path.join(distDir, `${appName}.js`);
89
+
90
+ // Step 1: Type-check with tsc (no emit)
91
+ console.log('🔍 Type-checking with TypeScript...');
86
92
  const tscCommand = tscPath === 'npx tsc'
87
- ? `npx tsc --project ${tsconfigPath} --outDir ${distDir} --rootDir ${appDir}/src`
88
- : `"${tscPath}" --project ${tsconfigPath} --outDir ${distDir} --rootDir ${appDir}/src`;
93
+ ? `npx tsc --project ${tsconfigPath} --noEmit`
94
+ : `"${tscPath}" --project ${tsconfigPath} --noEmit`;
89
95
 
90
96
  execSync(tscCommand, {
91
97
  stdio: 'inherit',
92
98
  cwd: appDir
93
99
  });
94
100
 
95
- // Rename app.js to {appName}.js
96
- const compiledFile = path.join(distDir, 'app.js');
97
- const targetFile = path.join(distDir, `${appName}.js`);
101
+ console.log('✅ Type-check passed\n');
102
+
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.
107
+ console.log('đŸ“Ļ Bundling with esbuild...');
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],
140
+ });
98
141
 
99
- if (fs.existsSync(compiledFile)) {
100
- fs.renameSync(compiledFile, targetFile);
142
+ if (result.errors.length > 0) {
143
+ console.error('❌ esbuild errors:', result.errors);
144
+ process.exit(1);
101
145
  }
102
146
 
103
- console.log('✅ TypeScript compiled\n');
147
+ console.log('✅ Bundle complete\n');
104
148
 
105
- // Step 2: Extract settings schema and inject into compiled JS
149
+ // Step 3: Extract settings schema and inject into compiled JS
106
150
  console.log('📋 Extracting settings schema...');
107
151
  const extractSchemaScript = path.join(sdkDir, 'scripts', 'extract-settings-schema.js');
108
- const sourceFile = path.join(appDir, 'src', 'app.ts');
109
152
 
110
- if (fs.existsSync(extractSchemaScript) && fs.existsSync(sourceFile)) {
153
+ if (fs.existsSync(extractSchemaScript) && fs.existsSync(entryPoint)) {
111
154
  try {
112
- execSync(`node "${extractSchemaScript}" "${sourceFile}" "${targetFile}"`, {
155
+ execSync(`node "${extractSchemaScript}" "${entryPoint}" "${targetFile}"`, {
113
156
  stdio: 'inherit',
114
157
  cwd: appDir
115
158
  });
@@ -120,15 +163,16 @@ try {
120
163
 
121
164
  console.log('');
122
165
 
123
- // Step 3: Run weave-build to transpile
166
+ // Step 4: Run weave-build to add header comment
124
167
  console.log('🔧 Transpiling to Weave format...');
125
168
  const buildScript = path.join(__dirname, 'build.js');
126
169
  execSync(`node "${buildScript}"`, {
127
170
  stdio: 'inherit',
128
171
  cwd: appDir
129
172
  });
173
+ }
130
174
 
131
- } catch (error) {
175
+ compile().catch((error) => {
132
176
  console.error('\n❌ Build failed');
133
177
  process.exit(1);
134
- }
178
+ });
@@ -0,0 +1,88 @@
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
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@weave-apps/sdk",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "SDK for building Weave Micro Apps",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -32,6 +32,7 @@
32
32
  "author": "Weave Platform",
33
33
  "license": "MIT",
34
34
  "dependencies": {
35
+ "esbuild": "^0.24.0",
35
36
  "typescript": "^5.9.3"
36
37
  },
37
38
  "devDependencies": {
@@ -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}`);