@scelar/nodepod 1.0.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/LICENSE +43 -0
- package/README.md +240 -0
- package/dist/child_process-BJOMsZje.js +8233 -0
- package/dist/child_process-BJOMsZje.js.map +1 -0
- package/dist/child_process-Cj8vOcuc.cjs +7434 -0
- package/dist/child_process-Cj8vOcuc.cjs.map +1 -0
- package/dist/index-Cb1Cgdnd.js +35308 -0
- package/dist/index-Cb1Cgdnd.js.map +1 -0
- package/dist/index-DsMGS-xc.cjs +37195 -0
- package/dist/index-DsMGS-xc.cjs.map +1 -0
- package/dist/index.cjs +65 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.mjs +59 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +95 -0
- package/src/__tests__/smoke.test.ts +11 -0
- package/src/constants/cdn-urls.ts +18 -0
- package/src/constants/config.ts +236 -0
- package/src/cross-origin.ts +26 -0
- package/src/engine-factory.ts +176 -0
- package/src/engine-types.ts +56 -0
- package/src/helpers/byte-encoding.ts +39 -0
- package/src/helpers/digest.ts +9 -0
- package/src/helpers/event-loop.ts +96 -0
- package/src/helpers/wasm-cache.ts +133 -0
- package/src/iframe-sandbox.ts +141 -0
- package/src/index.ts +192 -0
- package/src/isolation-helpers.ts +148 -0
- package/src/memory-volume.ts +941 -0
- package/src/module-transformer.ts +368 -0
- package/src/packages/archive-extractor.ts +248 -0
- package/src/packages/browser-bundler.ts +284 -0
- package/src/packages/installer.ts +396 -0
- package/src/packages/registry-client.ts +131 -0
- package/src/packages/version-resolver.ts +411 -0
- package/src/polyfills/assert.ts +384 -0
- package/src/polyfills/async_hooks.ts +144 -0
- package/src/polyfills/buffer.ts +628 -0
- package/src/polyfills/child_process.ts +2288 -0
- package/src/polyfills/chokidar.ts +336 -0
- package/src/polyfills/cluster.ts +106 -0
- package/src/polyfills/console.ts +136 -0
- package/src/polyfills/constants.ts +123 -0
- package/src/polyfills/crypto.ts +885 -0
- package/src/polyfills/dgram.ts +87 -0
- package/src/polyfills/diagnostics_channel.ts +76 -0
- package/src/polyfills/dns.ts +134 -0
- package/src/polyfills/domain.ts +68 -0
- package/src/polyfills/esbuild.ts +854 -0
- package/src/polyfills/events.ts +276 -0
- package/src/polyfills/fs.ts +2888 -0
- package/src/polyfills/fsevents.ts +79 -0
- package/src/polyfills/http.ts +1449 -0
- package/src/polyfills/http2.ts +199 -0
- package/src/polyfills/https.ts +76 -0
- package/src/polyfills/inspector.ts +62 -0
- package/src/polyfills/lightningcss.ts +105 -0
- package/src/polyfills/module.ts +191 -0
- package/src/polyfills/net.ts +353 -0
- package/src/polyfills/os.ts +238 -0
- package/src/polyfills/path.ts +206 -0
- package/src/polyfills/perf_hooks.ts +102 -0
- package/src/polyfills/process.ts +690 -0
- package/src/polyfills/punycode.ts +159 -0
- package/src/polyfills/querystring.ts +93 -0
- package/src/polyfills/quic.ts +118 -0
- package/src/polyfills/readdirp.ts +229 -0
- package/src/polyfills/readline.ts +692 -0
- package/src/polyfills/repl.ts +134 -0
- package/src/polyfills/rollup.ts +119 -0
- package/src/polyfills/sea.ts +33 -0
- package/src/polyfills/sqlite.ts +78 -0
- package/src/polyfills/stream.ts +1620 -0
- package/src/polyfills/string_decoder.ts +25 -0
- package/src/polyfills/tailwindcss-oxide.ts +309 -0
- package/src/polyfills/test.ts +197 -0
- package/src/polyfills/timers.ts +32 -0
- package/src/polyfills/tls.ts +105 -0
- package/src/polyfills/trace_events.ts +50 -0
- package/src/polyfills/tty.ts +71 -0
- package/src/polyfills/url.ts +174 -0
- package/src/polyfills/util.ts +559 -0
- package/src/polyfills/v8.ts +126 -0
- package/src/polyfills/vm.ts +132 -0
- package/src/polyfills/volume-registry.ts +15 -0
- package/src/polyfills/wasi.ts +44 -0
- package/src/polyfills/worker_threads.ts +326 -0
- package/src/polyfills/ws.ts +595 -0
- package/src/polyfills/zlib.ts +881 -0
- package/src/request-proxy.ts +716 -0
- package/src/script-engine.ts +3375 -0
- package/src/sdk/nodepod-fs.ts +93 -0
- package/src/sdk/nodepod-process.ts +86 -0
- package/src/sdk/nodepod-terminal.ts +350 -0
- package/src/sdk/nodepod.ts +509 -0
- package/src/sdk/types.ts +70 -0
- package/src/shell/commands/bun.ts +121 -0
- package/src/shell/commands/directory.ts +297 -0
- package/src/shell/commands/file-ops.ts +525 -0
- package/src/shell/commands/git.ts +2142 -0
- package/src/shell/commands/node.ts +80 -0
- package/src/shell/commands/npm.ts +198 -0
- package/src/shell/commands/pm-types.ts +45 -0
- package/src/shell/commands/pnpm.ts +82 -0
- package/src/shell/commands/search.ts +264 -0
- package/src/shell/commands/shell-env.ts +352 -0
- package/src/shell/commands/text-processing.ts +1152 -0
- package/src/shell/commands/yarn.ts +84 -0
- package/src/shell/shell-builtins.ts +19 -0
- package/src/shell/shell-helpers.ts +250 -0
- package/src/shell/shell-interpreter.ts +514 -0
- package/src/shell/shell-parser.ts +429 -0
- package/src/shell/shell-types.ts +85 -0
- package/src/syntax-transforms.ts +561 -0
- package/src/threading/engine-worker.ts +64 -0
- package/src/threading/inline-worker.ts +372 -0
- package/src/threading/offload-types.ts +112 -0
- package/src/threading/offload-worker.ts +383 -0
- package/src/threading/offload.ts +271 -0
- package/src/threading/process-context.ts +92 -0
- package/src/threading/process-handle.ts +275 -0
- package/src/threading/process-manager.ts +956 -0
- package/src/threading/process-worker-entry.ts +854 -0
- package/src/threading/shared-vfs.ts +352 -0
- package/src/threading/sync-channel.ts +135 -0
- package/src/threading/task-queue.ts +177 -0
- package/src/threading/vfs-bridge.ts +231 -0
- package/src/threading/worker-pool.ts +233 -0
- package/src/threading/worker-protocol.ts +358 -0
- package/src/threading/worker-vfs.ts +218 -0
- package/src/types/externals.d.ts +38 -0
- package/src/types/fs-streams.ts +142 -0
- package/src/types/manifest.ts +17 -0
- package/src/worker-sandbox.ts +90 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 R1ck404 / ScelarOrg
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
Commons Clause License Condition v1.0
|
|
24
|
+
|
|
25
|
+
The Software is provided to you by the Licensor under the License, as defined
|
|
26
|
+
below, subject to the following condition.
|
|
27
|
+
|
|
28
|
+
Without limiting other conditions in the License, the grant of rights under
|
|
29
|
+
the License will not include, and the License does not grant to you, the right
|
|
30
|
+
to Sell the Software.
|
|
31
|
+
|
|
32
|
+
For purposes of the foregoing, "Sell" means practicing any or all of the rights
|
|
33
|
+
granted to you under the License to provide to third parties, for a fee or
|
|
34
|
+
other consideration (including without limitation fees for hosting or
|
|
35
|
+
consulting/support services related to the Software), a product or service
|
|
36
|
+
whose value derives, entirely or substantially, from the functionality of the
|
|
37
|
+
Software. Any licensee who utilizes the Software as a component of a larger
|
|
38
|
+
application or service — where the primary value is derived from the licensee's
|
|
39
|
+
own original work — is not considered to be Selling the Software.
|
|
40
|
+
|
|
41
|
+
Licensor: R1ck404 / ScelarOrg
|
|
42
|
+
Software: Nodepod
|
|
43
|
+
License: MIT
|
package/README.md
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
# nodepod
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@scelar/nodepod)
|
|
4
|
+
[](https://github.com/ScelarOrg/Nodepod/blob/main/LICENSE)
|
|
5
|
+
|
|
6
|
+
Browser-native Node.js runtime. Run real Node.js code — filesystem, modules, `require()`, npm packages, HTTP servers — entirely inside the browser.
|
|
7
|
+
|
|
8
|
+
No backend. No containers. No WASM Node binary. Just polyfills, an in-memory filesystem, and a JavaScript execution engine.
|
|
9
|
+
|
|
10
|
+
Built by [@R1ck404](https://github.com/R1ck404) — powering [Scelar](https://scelar.com), the AI app builder that actually COMPLETELY builds your apps, from idea to production in a matter of minutes.
|
|
11
|
+
|
|
12
|
+
## Features
|
|
13
|
+
|
|
14
|
+
- **Virtual Filesystem** — Full in-memory `fs` API (read, write, watch, streams, symlinks, glob)
|
|
15
|
+
- **Module System** — `require()`, `import`, `module.exports`, `package.json` resolution, conditional exports
|
|
16
|
+
- **npm Packages** — Install real packages from the npm registry, extracted and resolved in-browser
|
|
17
|
+
- **HTTP Servers** — Run Express, Hono, Elysia, vite and other frameworks with real request/response routing
|
|
18
|
+
- **Shell** — Built-in bash-like shell with 35+ commands (ls, cat, grep, find, sed, etc.), pipes, redirections, variable expansion
|
|
19
|
+
- **Process Model** — Web Worker-based processes with `child_process.exec/spawn/fork`, `worker_threads`, and IPC
|
|
20
|
+
- **Terminal** — Drop-in xterm.js integration with line editing, history, raw mode, Ctrl+C
|
|
21
|
+
- **Preview** — Service Worker-based iframe preview with script injection and WebSocket bridging
|
|
22
|
+
- **Snapshots** — Save and restore the entire filesystem state
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install @scelar/nodepod
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Quick Start
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
import { Nodepod } from '@scelar/nodepod';
|
|
34
|
+
|
|
35
|
+
// Boot a nodepod instance with some files
|
|
36
|
+
const nodepod = await Nodepod.boot({
|
|
37
|
+
files: {
|
|
38
|
+
'/index.js': 'console.log("Hello from the browser!")',
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Run a script
|
|
43
|
+
const proc = await nodepod.spawn('node', ['index.js']);
|
|
44
|
+
proc.on('output', (text) => console.log(text));
|
|
45
|
+
await proc.completion;
|
|
46
|
+
|
|
47
|
+
// Read/write files
|
|
48
|
+
await nodepod.fs.writeFile('/data.json', JSON.stringify({ hello: 'world' }));
|
|
49
|
+
const content = await nodepod.fs.readFile('/data.json', 'utf8');
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Terminal Integration
|
|
53
|
+
|
|
54
|
+
nodepod provides built-in xterm.js terminal support:
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
import { Nodepod } from '@scelar/nodepod';
|
|
58
|
+
import { Terminal } from '@xterm/xterm';
|
|
59
|
+
import { FitAddon } from '@xterm/addon-fit';
|
|
60
|
+
|
|
61
|
+
const nodepod = await Nodepod.boot();
|
|
62
|
+
const terminal = nodepod.createTerminal({ Terminal, FitAddon });
|
|
63
|
+
terminal.attach('#terminal-container');
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The terminal handles line editing, command history, prompt rendering, raw/cooked mode, and streaming output out of the box.
|
|
67
|
+
|
|
68
|
+
## Running an Express Server
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
const nodepod = await Nodepod.boot({
|
|
72
|
+
files: {
|
|
73
|
+
'/server.js': `
|
|
74
|
+
const express = require('express');
|
|
75
|
+
const app = express();
|
|
76
|
+
app.get('/', (req, res) => res.json({ status: 'ok' }));
|
|
77
|
+
app.listen(3000, () => console.log('Server running on port 3000'));
|
|
78
|
+
`,
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Install express
|
|
83
|
+
await nodepod.install(['express']);
|
|
84
|
+
|
|
85
|
+
// Run the server
|
|
86
|
+
const proc = await nodepod.spawn('node', ['server.js']);
|
|
87
|
+
|
|
88
|
+
// Dispatch requests to the virtual server
|
|
89
|
+
const response = await nodepod.request(3000, 'GET', '/');
|
|
90
|
+
console.log(response.body); // { status: 'ok' }
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Preview Script Injection
|
|
94
|
+
|
|
95
|
+
Inject scripts into preview iframes before any page content loads — useful for setting up bridges between the main window and the iframe:
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
await nodepod.setPreviewScript(`
|
|
99
|
+
window.__bridge = {
|
|
100
|
+
sendToParent(msg) { window.parent.postMessage(msg, '*'); }
|
|
101
|
+
};
|
|
102
|
+
`);
|
|
103
|
+
|
|
104
|
+
// Remove it later
|
|
105
|
+
await nodepod.clearPreviewScript();
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## SDK API
|
|
109
|
+
|
|
110
|
+
### `Nodepod.boot(options?)`
|
|
111
|
+
|
|
112
|
+
Creates a fully wired nodepod instance.
|
|
113
|
+
|
|
114
|
+
| Option | Type | Default | Description |
|
|
115
|
+
|--------|------|---------|-------------|
|
|
116
|
+
| `files` | `Record<string, string>` | — | Initial files to populate the filesystem |
|
|
117
|
+
| `workdir` | `string` | `"/"` | Working directory |
|
|
118
|
+
| `env` | `Record<string, string>` | — | Environment variables |
|
|
119
|
+
| `swUrl` | `string` | — | Service Worker URL (enables preview iframes) |
|
|
120
|
+
| `watermark` | `boolean` | `true` | Show a small "nodepod" badge in preview iframes |
|
|
121
|
+
| `onServerReady` | `(port, url) => void` | — | Callback when a virtual server starts listening |
|
|
122
|
+
|
|
123
|
+
### Instance Methods
|
|
124
|
+
|
|
125
|
+
| Method | Description |
|
|
126
|
+
|--------|-------------|
|
|
127
|
+
| `spawn(cmd, args?)` | Run a command, returns `NodepodProcess` |
|
|
128
|
+
| `createTerminal(opts)` | Create an interactive terminal |
|
|
129
|
+
| `install(packages)` | Install npm packages |
|
|
130
|
+
| `fs.readFile(path, enc?)` | Read a file |
|
|
131
|
+
| `fs.writeFile(path, data)` | Write a file |
|
|
132
|
+
| `fs.readdir(path)` | List directory contents |
|
|
133
|
+
| `fs.stat(path)` | Get file stats |
|
|
134
|
+
| `fs.mkdir(path, opts?)` | Create a directory |
|
|
135
|
+
| `fs.rm(path, opts?)` | Remove files/directories |
|
|
136
|
+
| `snapshot()` | Capture filesystem state |
|
|
137
|
+
| `restore(snapshot)` | Restore filesystem from snapshot |
|
|
138
|
+
| `setPreviewScript(script)` | Inject JS into preview iframes |
|
|
139
|
+
| `clearPreviewScript()` | Remove injected preview script |
|
|
140
|
+
| `port(num)` | Get preview URL for a virtual server port |
|
|
141
|
+
|
|
142
|
+
### `NodepodProcess`
|
|
143
|
+
|
|
144
|
+
Returned by `spawn()`. An EventEmitter with:
|
|
145
|
+
|
|
146
|
+
| Event | Payload | Description |
|
|
147
|
+
|-------|---------|-------------|
|
|
148
|
+
| `output` | `string` | stdout data |
|
|
149
|
+
| `error` | `string` | stderr data |
|
|
150
|
+
| `exit` | `number` | exit code |
|
|
151
|
+
|
|
152
|
+
Property: `completion` — a `Promise<void>` that resolves when the process exits.
|
|
153
|
+
|
|
154
|
+
## Architecture
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
nodepod
|
|
158
|
+
├── src/
|
|
159
|
+
│ ├── script-engine.ts # JavaScript execution engine (require, ESM→CJS, REPL)
|
|
160
|
+
│ ├── memory-volume.ts # In-memory virtual filesystem
|
|
161
|
+
│ ├── syntax-transforms.ts # ESM-to-CJS conversion via acorn
|
|
162
|
+
│ ├── module-transformer.ts # esbuild-wasm code transforms
|
|
163
|
+
│ ├── polyfills/ # Node.js built-in module polyfills
|
|
164
|
+
│ │ ├── fs.ts # Filesystem (read, write, watch, streams, glob)
|
|
165
|
+
│ │ ├── http.ts # HTTP server/client
|
|
166
|
+
│ │ ├── stream.ts # Readable, Writable, Transform, Duplex
|
|
167
|
+
│ │ ├── events.ts # EventEmitter
|
|
168
|
+
│ │ ├── path.ts # Path operations
|
|
169
|
+
│ │ ├── crypto.ts # Hashing, randomBytes, randomUUID
|
|
170
|
+
│ │ ├── child_process.ts # exec, spawn, fork, execSync
|
|
171
|
+
│ │ ├── net.ts # TCP Socket, Server
|
|
172
|
+
│ │ └── ... # 40+ more polyfills
|
|
173
|
+
│ ├── shell/ # Bash-like shell interpreter
|
|
174
|
+
│ │ ├── shell-parser.ts # Tokenizer + recursive-descent parser
|
|
175
|
+
│ │ ├── shell-builtins.ts # 35+ built-in commands
|
|
176
|
+
│ │ └── shell-interpreter.ts # AST executor, pipes, redirections
|
|
177
|
+
│ ├── packages/ # npm package management
|
|
178
|
+
│ │ ├── installer.ts # Package installer
|
|
179
|
+
│ │ ├── registry-client.ts # npm registry client
|
|
180
|
+
│ │ └── version-resolver.ts # Semver resolution
|
|
181
|
+
│ ├── threading/ # Worker-based process model
|
|
182
|
+
│ │ ├── process-manager.ts # Process lifecycle management
|
|
183
|
+
│ │ └── process-handle.ts # Process I/O handle
|
|
184
|
+
│ └── sdk/ # Public SDK layer
|
|
185
|
+
│ ├── nodepod.ts # Nodepod.boot() entry point
|
|
186
|
+
│ ├── nodepod-fs.ts # Async filesystem facade
|
|
187
|
+
│ ├── nodepod-process.ts # Process handle
|
|
188
|
+
│ └── nodepod-terminal.ts # xterm.js terminal integration
|
|
189
|
+
└── static/
|
|
190
|
+
└── __sw__.js # Service Worker for HTTP request interception
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Supported Node.js Modules
|
|
194
|
+
|
|
195
|
+
**Full implementations:** fs, path, events, stream, buffer, process, http, https, net, crypto, zlib, url, querystring, util, os, tty, child_process, assert, readline, module, timers, string_decoder, perf_hooks, constants, punycode
|
|
196
|
+
|
|
197
|
+
**Shims/stubs:** dns, worker_threads, vm, v8, tls, dgram, cluster, http2, inspector, domain, diagnostics_channel, async_hooks
|
|
198
|
+
|
|
199
|
+
## CDN Usage
|
|
200
|
+
Once published to npm, nodepod is automatically available on CDNs:
|
|
201
|
+
|
|
202
|
+
```html
|
|
203
|
+
<!-- unpkg -->
|
|
204
|
+
<script src="https://unpkg.com/@scelar/nodepod"></script>
|
|
205
|
+
|
|
206
|
+
<!-- jsDelivr -->
|
|
207
|
+
<script src="https://cdn.jsdelivr.net/npm/@scelar/nodepod"></script>
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Development
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
git clone https://github.com/ScelarOrg/Nodepod.git
|
|
214
|
+
cd Nodepod
|
|
215
|
+
npm install
|
|
216
|
+
npm run type-check # TypeScript validation
|
|
217
|
+
npm run build:lib # Build ESM + CJS bundles
|
|
218
|
+
npm test # Run tests
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## Contributing
|
|
222
|
+
|
|
223
|
+
See [CONTRIBUTING.md](./CONTRIBUTING.md) for setup instructions and guidelines.
|
|
224
|
+
|
|
225
|
+
## Author
|
|
226
|
+
|
|
227
|
+
Created by [@R1ck404](https://github.com/R1ck404). Part of the [Scelar](https://scelar.com) ecosystem.
|
|
228
|
+
|
|
229
|
+
## License
|
|
230
|
+
|
|
231
|
+
[MIT + Commons Clause](./LICENSE)
|
|
232
|
+
|
|
233
|
+
This project uses the MIT license with the [Commons Clause](https://commonsclause.com/) restriction. In plain terms:
|
|
234
|
+
|
|
235
|
+
- **Use it** freely in your own projects, including commercial ones
|
|
236
|
+
- **Modify it**, fork it, learn from it
|
|
237
|
+
- **Ship products** built with nodepod — that's totally fine
|
|
238
|
+
- **Don't resell nodepod itself** as a standalone product or hosted service
|
|
239
|
+
|
|
240
|
+
Basically, build whatever you want with it — just don't take nodepod, rebrand it, and sell it as your own thing.
|