@weave-apps/sdk 0.1.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/README.md ADDED
@@ -0,0 +1,241 @@
1
+ # Weave App SDK
2
+
3
+ Official SDK for building third-party applications for the Weave Platform.
4
+
5
+ ## Features
6
+
7
+ - ✅ **TypeScript Support** - Write apps in TypeScript with full type safety
8
+ - ✅ **Clean JavaScript Output** - Transpiles to readable, unobfuscated JavaScript
9
+ - ✅ **Security Review Ready** - Generated code is easy to audit
10
+ - ✅ **Project Scaffolding** - Quick start with `weave-init`
11
+ - ✅ **DOM API** - Secure parent page DOM manipulation
12
+ - ✅ **Shadow DOM Isolation** - Scoped styles and encapsulation
13
+
14
+ ## Installation
15
+
16
+ Install directly from GitHub:
17
+
18
+ ```bash
19
+ npm install github:your-org/weave-app-sdk
20
+ ```
21
+
22
+ Or add to your `package.json`:
23
+
24
+ ```json
25
+ {
26
+ "devDependencies": {
27
+ "@weave/app-sdk": "github:your-org/weave-app-sdk"
28
+ }
29
+ }
30
+ ```
31
+
32
+ ## Quick Start
33
+
34
+ ### 1. Initialize a New App
35
+
36
+ ```bash
37
+ npx weave-init my-custom-app
38
+ cd my-custom-app
39
+ npm install
40
+ ```
41
+
42
+ ### 2. Develop Your App
43
+
44
+ Edit `src/app.ts`:
45
+
46
+ ```typescript
47
+ import { WeaveBaseApp } from '@weave/app-sdk';
48
+
49
+ class MyCustomApp extends WeaveBaseApp {
50
+ constructor() {
51
+ super({
52
+ id: 'my-custom-app',
53
+ name: 'My Custom App',
54
+ version: '1.0.0',
55
+ category: 'utility',
56
+ description: 'My awesome app',
57
+ author: 'Your Name',
58
+ tags: ['custom']
59
+ });
60
+ }
61
+
62
+ protected render(): void {
63
+ this.renderHTML(`
64
+ <style>
65
+ .container { padding: 20px; }
66
+ button { padding: 10px 20px; }
67
+ </style>
68
+ <div class="container">
69
+ <h1>Hello Weave!</h1>
70
+ <button id="myBtn">Click Me</button>
71
+ </div>
72
+ `);
73
+ }
74
+
75
+ protected setupEventListeners(): void {
76
+ this.query('#myBtn')?.addEventListener('click', () => {
77
+ alert('Button clicked!');
78
+ });
79
+ }
80
+ }
81
+
82
+ customElements.define('my-custom-app', MyCustomApp);
83
+ ```
84
+
85
+ ### 3. Build Your App
86
+
87
+ ```bash
88
+ npm run build
89
+ ```
90
+
91
+ This runs:
92
+ 1. `tsc` - Compiles TypeScript to JavaScript
93
+ 2. `weave-build` - Transpiles to clean, platform-ready JavaScript
94
+
95
+ Output: `dist/app.js` - ready for upload to Weave Platform.
96
+
97
+ ### 4. Upload to Weave
98
+
99
+ Upload the generated `dist/app.js` file to the Weave Platform via the Enterprise Management Console.
100
+
101
+ ## API Reference
102
+
103
+ ### WeaveBaseApp
104
+
105
+ Base class for all Weave apps.
106
+
107
+ #### Constructor
108
+
109
+ ```typescript
110
+ constructor(appInfo: WeaveAppInfo)
111
+ ```
112
+
113
+ **App Info:**
114
+ - `id` (string) - Unique identifier (kebab-case)
115
+ - `name` (string) - Display name
116
+ - `version` (string) - Semantic version (x.y.z)
117
+ - `category` (string) - App category
118
+ - `description` (string) - Detailed description
119
+ - `author` (string) - Author name
120
+ - `tags` (string[]) - Optional tags
121
+
122
+ #### Methods to Implement
123
+
124
+ ##### `render(): void`
125
+ Render your app's UI. Use `this.renderHTML()` to inject HTML.
126
+
127
+ ##### `setupEventListeners(): void` (Optional)
128
+ Setup event listeners for your app's elements.
129
+
130
+ ##### `cleanup(): void` (Optional)
131
+ Cleanup when app is removed from DOM.
132
+
133
+ #### Helper Methods
134
+
135
+ ##### `renderHTML(html: string): void`
136
+ Renders HTML into the shadow root.
137
+
138
+ ##### `query<T>(selector: string): T | null`
139
+ Query a single element in shadow root.
140
+
141
+ ##### `queryAll<T>(selector: string): NodeListOf<T>`
142
+ Query all matching elements in shadow root.
143
+
144
+ ##### `setState(updates: object): void`
145
+ Update app state.
146
+
147
+ ### WeaveDOMAPI
148
+
149
+ API for interacting with the parent page DOM.
150
+
151
+ **Available globally as `window.weaveDOM`**
152
+
153
+ #### Read Operations
154
+
155
+ ```typescript
156
+ // Query element
157
+ const element = await window.weaveDOM.query('h1');
158
+
159
+ // Get text content
160
+ const text = await window.weaveDOM.getText('h1');
161
+
162
+ // Get attribute
163
+ const href = await window.weaveDOM.getAttribute('a', 'href');
164
+
165
+ // Check if element has class
166
+ const hasClass = await window.weaveDOM.hasClass('.btn', 'active');
167
+ ```
168
+
169
+ #### Write Operations
170
+
171
+ ```typescript
172
+ // Set text
173
+ await window.weaveDOM.setText('h1', 'New Title');
174
+
175
+ // Add class
176
+ await window.weaveDOM.addClass('.btn', 'active');
177
+
178
+ // Set style
179
+ await window.weaveDOM.setStyle('h1', 'color', 'red');
180
+
181
+ // Set attribute
182
+ await window.weaveDOM.setAttribute('input', 'placeholder', 'Enter text');
183
+ ```
184
+
185
+ #### DOM Manipulation
186
+
187
+ ```typescript
188
+ // Insert HTML
189
+ await window.weaveDOM.insertHTML('.container', '<p>New content</p>', 'beforeend');
190
+
191
+ // Remove element
192
+ await window.weaveDOM.removeElement('.old-element');
193
+ ```
194
+
195
+ ## Build Process
196
+
197
+ The build uses two commands:
198
+
199
+ 1. **`tsc`** - TypeScript compiler (ES2020 target)
200
+ 2. **`weave-build`** - SDK's build tool that:
201
+ - Removes import statements (SDK is global)
202
+ - Replaces SDK references with `window.*`
203
+ - Adds header comment
204
+ - Generates clean, readable JavaScript
205
+
206
+ You can also run `weave-build` manually after compiling:
207
+ ```bash
208
+ tsc
209
+ weave-build
210
+ ```
211
+
212
+ ## Security
213
+
214
+ - ✅ **Selector Validation** - Prevents dangerous selectors
215
+ - ✅ **HTML Sanitization** - Removes scripts and event handlers
216
+ - ✅ **Attribute Whitelist** - Blocks dangerous attributes
217
+ - ✅ **Shadow DOM Isolation** - Scoped styles and encapsulation
218
+ - ✅ **No Direct DOM Access** - All operations go through secure bridge
219
+
220
+ ## Project Structure
221
+
222
+ ```
223
+ my-app/
224
+ ├── src/
225
+ │ └── app.ts # Your TypeScript source
226
+ ├── dist/
227
+ │ └── app.js # Compiled JavaScript (upload this)
228
+ ├── package.json
229
+ ├── tsconfig.json
230
+ └── README.md
231
+ ```
232
+
233
+ **Note:** The build script (`weave-build`) is part of the SDK, not copied to your app.
234
+
235
+ ## Examples
236
+
237
+ See the [examples directory](./examples) for complete app examples.
238
+
239
+ ## License
240
+
241
+ MIT
package/bin/build.js ADDED
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Weave App Builder
5
+ *
6
+ * Transpiles TypeScript to clean JavaScript for Weave Platform
7
+ */
8
+
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+
12
+ // Get the app directory (where this is being run from)
13
+ const appDir = process.cwd();
14
+
15
+ // Get app name from package.json
16
+ let appName = 'app';
17
+ let appDisplayName = 'Weave App';
18
+ try {
19
+ const packageJson = JSON.parse(fs.readFileSync(path.join(appDir, 'package.json'), 'utf8'));
20
+ appName = packageJson.name || appName;
21
+ appDisplayName = packageJson.name || appDisplayName;
22
+ } catch (e) {
23
+ console.error('❌ Error: Could not read package.json');
24
+ process.exit(1);
25
+ }
26
+
27
+ const distFile = path.join(appDir, 'dist', `${appName}.js`);
28
+
29
+ // Check if dist file exists
30
+ if (!fs.existsSync(distFile)) {
31
+ console.error(`❌ Error: dist/${appName}.js not found`);
32
+ console.error('Run "tsc" first to compile TypeScript');
33
+ process.exit(1);
34
+ }
35
+
36
+ console.log('🔨 Building Weave app...\n');
37
+
38
+ // Read compiled TypeScript output
39
+ let code = fs.readFileSync(distFile, 'utf8');
40
+
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)
53
+ code = code.replace(/(?<!window\.)weaveDOM\./g, 'window.weaveDOM.');
54
+
55
+ // Replace weaveAPI with window.weaveAPI (but not if already prefixed)
56
+ code = code.replace(/(?<!window\.)weaveAPI\./g, 'window.weaveAPI.');
57
+
58
+ // Remove empty lines created by removals
59
+ code = code.replace(/\n\n\n+/g, '\n\n');
60
+
61
+ // Add header comment
62
+ const header = `/**
63
+ * ${appDisplayName}
64
+ *
65
+ * Built with Weave App SDK
66
+ * Generated: ${new Date().toISOString()}
67
+ */
68
+
69
+ // Access SDK from window globals
70
+ const { WeaveBaseApp, weaveDOM } = window;
71
+
72
+ `;
73
+
74
+ code = header + code;
75
+
76
+ // Write final JavaScript
77
+ fs.writeFileSync(distFile, code);
78
+
79
+ console.log('✅ Build complete:', distFile);
80
+ console.log('📦 Ready to upload to Weave Platform\n');
81
+ console.log('File size:', Math.round(code.length / 1024), 'KB');
package/bin/compile.js ADDED
@@ -0,0 +1,109 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Weave App Compiler
5
+ *
6
+ * Compiles TypeScript and transpiles to Weave-ready JavaScript
7
+ */
8
+
9
+ const { execSync } = require('child_process');
10
+ const path = require('path');
11
+ const fs = require('fs');
12
+
13
+ // Get the app directory (where this is being run from)
14
+ const appDir = process.cwd();
15
+
16
+ // Get app name from package.json
17
+ let appName = 'app';
18
+ try {
19
+ const packageJson = JSON.parse(fs.readFileSync(path.join(appDir, 'package.json'), 'utf8'));
20
+ appName = packageJson.name || appName;
21
+ } catch (e) {
22
+ console.error('❌ Error: Could not read package.json');
23
+ process.exit(1);
24
+ }
25
+
26
+ console.log('🔨 Compiling Weave app...\n');
27
+
28
+ // Check if src directory exists
29
+ if (!fs.existsSync(path.join(appDir, 'src'))) {
30
+ console.error('❌ Error: src/ directory not found');
31
+ console.error('Make sure you are in the app directory');
32
+ process.exit(1);
33
+ }
34
+
35
+ // Get SDK directory (where this script is located)
36
+ const sdkDir = path.join(__dirname, '..');
37
+ const tscPath = path.join(sdkDir, 'node_modules', '.bin', 'tsc');
38
+
39
+ // Use app's tsconfig.json (which extends SDK's config)
40
+ const tsconfigPath = path.join(appDir, 'tsconfig.json');
41
+
42
+ // Check if tsconfig exists in app
43
+ if (!fs.existsSync(tsconfigPath)) {
44
+ console.error('❌ Error: tsconfig.json not found in app directory');
45
+ console.error('Make sure you ran weave-init to create the project');
46
+ process.exit(1);
47
+ }
48
+
49
+ // Check if TypeScript exists in SDK
50
+ if (!fs.existsSync(tscPath)) {
51
+ console.error('❌ Error: TypeScript not found in SDK');
52
+ console.error('Run: cd SDK && npm install');
53
+ process.exit(1);
54
+ }
55
+
56
+ try {
57
+ // Create dist directory if it doesn't exist
58
+ const distDir = path.join(appDir, 'dist');
59
+ if (!fs.existsSync(distDir)) {
60
+ fs.mkdirSync(distDir, { recursive: true });
61
+ }
62
+
63
+ // Step 1: Compile TypeScript using SDK's tsconfig and TypeScript
64
+ console.log('📦 Compiling TypeScript...');
65
+ execSync(`${tscPath} --project ${tsconfigPath} --outDir ${distDir} --rootDir ${appDir}/src`, {
66
+ stdio: 'inherit',
67
+ cwd: appDir
68
+ });
69
+
70
+ // Rename app.js to {appName}.js
71
+ const compiledFile = path.join(distDir, 'app.js');
72
+ const targetFile = path.join(distDir, `${appName}.js`);
73
+
74
+ if (fs.existsSync(compiledFile)) {
75
+ fs.renameSync(compiledFile, targetFile);
76
+ }
77
+
78
+ console.log('✅ TypeScript compiled\n');
79
+
80
+ // Step 2: Extract settings schema and inject into compiled JS
81
+ console.log('📋 Extracting settings schema...');
82
+ const extractSchemaScript = path.join(sdkDir, 'scripts', 'extract-settings-schema.js');
83
+ const sourceFile = path.join(appDir, 'src', 'app.ts');
84
+
85
+ if (fs.existsSync(extractSchemaScript) && fs.existsSync(sourceFile)) {
86
+ try {
87
+ execSync(`node ${extractSchemaScript} ${sourceFile} ${targetFile}`, {
88
+ stdio: 'inherit',
89
+ cwd: appDir
90
+ });
91
+ } catch (error) {
92
+ console.warn('⚠️ Schema extraction failed (non-fatal)');
93
+ }
94
+ }
95
+
96
+ console.log('');
97
+
98
+ // Step 3: Run weave-build to transpile
99
+ console.log('🔧 Transpiling to Weave format...');
100
+ const buildScript = path.join(__dirname, 'build.js');
101
+ execSync(`node ${buildScript}`, {
102
+ stdio: 'inherit',
103
+ cwd: appDir
104
+ });
105
+
106
+ } catch (error) {
107
+ console.error('\n❌ Build failed');
108
+ process.exit(1);
109
+ }
package/bin/init.js ADDED
@@ -0,0 +1,251 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Weave App Initializer
5
+ *
6
+ * Creates a new Weave app project with TypeScript setup
7
+ */
8
+
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+ const { execSync } = require('child_process');
12
+
13
+ const args = process.argv.slice(2);
14
+ const appName = args[0];
15
+
16
+ if (!appName) {
17
+ console.error('❌ Error: App name is required');
18
+ console.log('Usage: npx weave-init <app-name>');
19
+ process.exit(1);
20
+ }
21
+
22
+ // Validate app name (kebab-case)
23
+ if (!/^[a-z][a-z0-9-]*$/.test(appName)) {
24
+ console.error('❌ Error: App name must be in kebab-case (e.g., my-custom-app)');
25
+ process.exit(1);
26
+ }
27
+
28
+ const projectDir = path.join(process.cwd(), appName);
29
+
30
+ // Check if directory already exists
31
+ if (fs.existsSync(projectDir)) {
32
+ console.error(`❌ Error: Directory "${appName}" already exists`);
33
+ process.exit(1);
34
+ }
35
+
36
+ console.log(`\n🚀 Creating Weave app: ${appName}\n`);
37
+
38
+ // Create project directory
39
+ fs.mkdirSync(projectDir, { recursive: true });
40
+ fs.mkdirSync(path.join(projectDir, 'src'), { recursive: true });
41
+
42
+ // Create package.json
43
+ const packageJson = {
44
+ name: appName,
45
+ version: '1.0.0',
46
+ description: 'A Weave app',
47
+ main: `dist/${appName}.js`,
48
+ scripts: {
49
+ build: 'weave-compile',
50
+ clean: 'rm -rf dist'
51
+ },
52
+ devDependencies: {
53
+ '@weave/app-sdk': 'github:your-org/weave-app-sdk'
54
+ }
55
+ };
56
+
57
+ fs.writeFileSync(
58
+ path.join(projectDir, 'package.json'),
59
+ JSON.stringify(packageJson, null, 2)
60
+ );
61
+
62
+ // Create minimal tsconfig.json that extends SDK's config
63
+ // This is needed for IDE support (IntelliSense, type checking, etc.)
64
+ const tsConfig = {
65
+ extends: '@weave/app-sdk/tsconfig.app.json',
66
+ compilerOptions: {
67
+ baseUrl: '.',
68
+ paths: {
69
+ '@weave/app-sdk': ['node_modules/@weave/app-sdk/dist']
70
+ }
71
+ },
72
+ include: ['src/**/*'],
73
+ exclude: ['node_modules', 'dist']
74
+ };
75
+
76
+ fs.writeFileSync(
77
+ path.join(projectDir, 'tsconfig.json'),
78
+ JSON.stringify(tsConfig, null, 2)
79
+ );
80
+
81
+ // Copy app template from SDK
82
+ const templateDir = path.join(__dirname, '..', 'templates');
83
+ const appTemplate = `import { WeaveBaseApp, WeaveAppInfo } from '@weave/app-sdk';
84
+
85
+ // Define settings type for this app, this is a way of injecting settings into the app from the Enterprise Console
86
+ // These will be auto-extracted and displayed in the Enterprise Console
87
+ interface ${toPascalCase(appName)}Settings {
88
+ /**
89
+ * @description Example setting - API endpoint URL
90
+ * @default "https://api.example.com"
91
+ */
92
+ apiEndpoint?: string;
93
+
94
+ /**
95
+ * @description Example setting - Enable debug mode
96
+ * @default false
97
+ */
98
+ debugMode?: boolean;
99
+ }
100
+
101
+ // Define state type for this app
102
+ interface ${toPascalCase(appName)}State {
103
+ count: number;
104
+ lastUpdated?: Date;
105
+ }
106
+
107
+ class ${toPascalCase(appName)} extends WeaveBaseApp<${toPascalCase(appName)}Settings, ${toPascalCase(appName)}State> {
108
+ constructor() {
109
+ super({
110
+ id: '${appName}',
111
+ name: '${toTitleCase(appName)}',
112
+ version: '1.0.0',
113
+ category: 'utility',
114
+ description: 'My custom Weave app',
115
+ author: 'Your Name',
116
+ tags: ['custom']
117
+ });
118
+
119
+ // Initialize app state with type safety
120
+ this.state = {
121
+ count: 0
122
+ };
123
+
124
+ // Access settings with type safety
125
+ console.log('API Endpoint:', this.appSettings?.apiEndpoint);
126
+ console.log('Debug Mode:', this.appSettings?.debugMode);
127
+ }
128
+
129
+ protected render(): void {
130
+ this.renderHTML(\`
131
+ <style>
132
+ * {
133
+ margin: 0;
134
+ padding: 0;
135
+ box-sizing: border-box;
136
+ }
137
+
138
+ .app-container {
139
+ padding: 20px;
140
+ font-family: system-ui, -apple-system, sans-serif;
141
+ }
142
+
143
+ h1 {
144
+ font-size: 1.5rem;
145
+ margin-bottom: 1rem;
146
+ color: #111827;
147
+ }
148
+
149
+ button {
150
+ padding: 10px 20px;
151
+ background: #4f46e5;
152
+ color: white;
153
+ border: none;
154
+ border-radius: 6px;
155
+ cursor: pointer;
156
+ font-size: 14px;
157
+ font-weight: 500;
158
+ }
159
+
160
+ button:hover {
161
+ background: #4338ca;
162
+ }
163
+ </style>
164
+
165
+ <div class="app-container">
166
+ <h1>${toTitleCase(appName)}</h1>
167
+ <p>Count: <span id="count">0</span></p>
168
+ <button id="incrementBtn">Increment</button>
169
+ </div>
170
+ \`);
171
+ }
172
+
173
+ protected setupEventListeners(): void {
174
+ this.query('#incrementBtn')?.addEventListener('click', () => {
175
+ this.handleIncrement();
176
+ });
177
+ }
178
+
179
+ private handleIncrement(): void {
180
+ this.setState({ count: this.state.count + 1 });
181
+ const countEl = this.query('#count');
182
+ if (countEl) {
183
+ countEl.textContent = String(this.state.count);
184
+ }
185
+ }
186
+ }
187
+
188
+ // Register custom element
189
+ customElements.define('${appName}', ${toPascalCase(appName)});
190
+ `;
191
+
192
+ fs.writeFileSync(path.join(projectDir, 'src', 'app.ts'), appTemplate);
193
+
194
+ // Create README from template
195
+ const readmePath = path.join(__dirname, '..', 'templates', 'README.md');
196
+ if (fs.existsSync(readmePath)) {
197
+ let readmeContent = fs.readFileSync(readmePath, 'utf8');
198
+ // Replace placeholders
199
+ readmeContent = readmeContent.replace(/\{\{APP_NAME_TITLE\}\}/g, toTitleCase(appName));
200
+ readmeContent = readmeContent.replace(/\{\{APP_NAME\}\}/g, appName);
201
+ fs.writeFileSync(path.join(projectDir, 'README.md'), readmeContent);
202
+ } else {
203
+ console.warn('⚠️ Warning: README.md template not found in SDK templates');
204
+ }
205
+
206
+ // Copy SIDEKICK_SPEC.md from SDK templates
207
+ const specPath = path.join(__dirname, '..', 'templates', 'SIDEKICK_SPEC.md');
208
+ if (fs.existsSync(specPath)) {
209
+ const specContent = fs.readFileSync(specPath, 'utf8');
210
+ fs.writeFileSync(path.join(projectDir, 'SIDEKICK_SPEC.md'), specContent);
211
+ } else {
212
+ console.warn('⚠️ Warning: SIDEKICK_SPEC.md not found in SDK templates');
213
+ }
214
+
215
+ // Create .gitignore
216
+ const gitignore = `node_modules/
217
+ dist/
218
+ *.log
219
+ .DS_Store
220
+ `;
221
+
222
+ fs.writeFileSync(path.join(projectDir, '.gitignore'), gitignore);
223
+
224
+ console.log('✅ Project created successfully!\n');
225
+ console.log('📁 Project structure:');
226
+ console.log(` ${appName}/`);
227
+ console.log(' ├── src/');
228
+ console.log(' │ └── app.ts');
229
+ console.log(' ├── package.json');
230
+ console.log(' ├── tsconfig.json (extends SDK config)');
231
+ console.log(' ├── SIDEKICK_SPEC.md (AI assistant guide)');
232
+ console.log(' └── README.md\n');
233
+ console.log('🚀 Next steps:');
234
+ console.log(` cd ${appName}`);
235
+ console.log(' npm install');
236
+ console.log(' npm run build\n');
237
+
238
+ // Helper functions
239
+ function toPascalCase(str) {
240
+ return str
241
+ .split('-')
242
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1))
243
+ .join('');
244
+ }
245
+
246
+ function toTitleCase(str) {
247
+ return str
248
+ .split('-')
249
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1))
250
+ .join(' ');
251
+ }