@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 +241 -0
- package/bin/build.js +81 -0
- package/bin/compile.js +109 -0
- package/bin/init.js +251 -0
- package/dist/SidekickAPIClient.d.ts +231 -0
- package/dist/SidekickAPIClient.d.ts.map +1 -0
- package/dist/SidekickAPIClient.js +274 -0
- package/dist/SidekickBaseApp.d.ts +177 -0
- package/dist/SidekickBaseApp.d.ts.map +1 -0
- package/dist/SidekickBaseApp.js +228 -0
- package/dist/SidekickDOMAPI.d.ts +433 -0
- package/dist/SidekickDOMAPI.d.ts.map +1 -0
- package/dist/SidekickDOMAPI.js +620 -0
- package/dist/WeaveAPIClient.d.ts +231 -0
- package/dist/WeaveAPIClient.d.ts.map +1 -0
- package/dist/WeaveAPIClient.js +274 -0
- package/dist/WeaveBaseApp.d.ts +177 -0
- package/dist/WeaveBaseApp.d.ts.map +1 -0
- package/dist/WeaveBaseApp.js +229 -0
- package/dist/WeaveDOMAPI.d.ts +433 -0
- package/dist/WeaveDOMAPI.d.ts.map +1 -0
- package/dist/WeaveDOMAPI.js +620 -0
- package/dist/global.d.ts +19 -0
- package/dist/global.d.ts.map +1 -0
- package/dist/global.js +17 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/package.json +45 -0
- package/scripts/copy-sdk-files.js +57 -0
- package/scripts/extract-settings-schema.js +235 -0
- package/templates/README.md +202 -0
- package/templates/WEAVE_SPEC.md +3703 -0
- package/tsconfig.app.json +17 -0
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
|
+
}
|