@sansavision/create-pulse 0.1.0-alpha.1
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 +58 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +115 -0
- package/package.json +32 -0
- package/src/index.ts +114 -0
- package/templates/react-all-features/index.html +12 -0
- package/templates/react-all-features/package-lock.json +2683 -0
- package/templates/react-all-features/package.json +27 -0
- package/templates/react-all-features/src/App.tsx +201 -0
- package/templates/react-all-features/src/components/VideoPlayer.tsx +113 -0
- package/templates/react-all-features/src/hooks/usePulse.ts +113 -0
- package/templates/react-all-features/src/index.css +38 -0
- package/templates/react-all-features/src/main.tsx +10 -0
- package/templates/react-all-features/tailwind.config.js +55 -0
- package/templates/react-all-features/tsconfig.json +25 -0
- package/templates/react-all-features/tsconfig.node.json +11 -0
- package/templates/react-all-features/tsconfig.node.tsbuildinfo +1 -0
- package/templates/react-all-features/tsconfig.tsbuildinfo +1 -0
- package/templates/react-all-features/vite.config.d.ts +2 -0
- package/templates/react-all-features/vite.config.js +6 -0
- package/templates/react-all-features/vite.config.ts +7 -0
- package/templates/react-watch-together/index.html +12 -0
- package/templates/react-watch-together/package-lock.json +2683 -0
- package/templates/react-watch-together/package.json +27 -0
- package/templates/react-watch-together/src/App.tsx +165 -0
- package/templates/react-watch-together/src/components/VideoPlayer.tsx +113 -0
- package/templates/react-watch-together/src/hooks/usePulse.ts +113 -0
- package/templates/react-watch-together/src/index.css +38 -0
- package/templates/react-watch-together/src/main.tsx +10 -0
- package/templates/react-watch-together/tailwind.config.js +55 -0
- package/templates/react-watch-together/tsconfig.json +25 -0
- package/templates/react-watch-together/tsconfig.node.json +11 -0
- package/templates/react-watch-together/tsconfig.node.tsbuildinfo +1 -0
- package/templates/react-watch-together/tsconfig.tsbuildinfo +1 -0
- package/templates/react-watch-together/vite.config.d.ts +2 -0
- package/templates/react-watch-together/vite.config.js +6 -0
- package/templates/react-watch-together/vite.config.ts +7 -0
- package/tsconfig.json +13 -0
package/README.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# @sansavision/create-pulse
|
|
2
|
+
|
|
3
|
+
The official scaffolding tool for creating Pulse applications.
|
|
4
|
+
|
|
5
|
+
`create-pulse` generates a battle-tested, TypeScript-ready React application pre-configured to connect to a Pulse Relay. It eliminates the boilerplate of setting up WebSocket/WebTransport connections and provides robust React hooks.
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
You can create a new Pulse project interactively by running:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm create pulse@latest
|
|
13
|
+
# or
|
|
14
|
+
npx @sansavision/create-pulse
|
|
15
|
+
# or
|
|
16
|
+
bunx @sansavision/create-pulse
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
You can optionally specify the project name directly:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npx @sansavision/create-pulse my-pulse-app
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Available Templates
|
|
26
|
+
|
|
27
|
+
When you run `create-pulse`, you'll be prompted to select a template.
|
|
28
|
+
|
|
29
|
+
### 1. Watch Together (React + TS)
|
|
30
|
+
A clean implementation of the "Watch Together" synchronized video player.
|
|
31
|
+
- Features a custom `usePulse` hook for lifecycle management.
|
|
32
|
+
- Handles syncing `play`, `pause`, and `seek` events across clients natively via the Pulse Relay.
|
|
33
|
+
- Built with React, Vite, Tailwind CSS, and TypeScript.
|
|
34
|
+
|
|
35
|
+
### 2. All Features (React + TS)
|
|
36
|
+
A kitchen-sink boilerplate that integrates multiple features.
|
|
37
|
+
- Perfect for exploring the SDK limits.
|
|
38
|
+
- Contains stubs and route setups for Chat, Metrics, and Video sync.
|
|
39
|
+
- Built with React, Vite, Tailwind CSS, and TypeScript.
|
|
40
|
+
|
|
41
|
+
## Running Your Scaffolded App
|
|
42
|
+
|
|
43
|
+
After your app is generated:
|
|
44
|
+
|
|
45
|
+
1. Navigate to the project directory:
|
|
46
|
+
```bash
|
|
47
|
+
cd my-pulse-app
|
|
48
|
+
```
|
|
49
|
+
2. Install dependencies:
|
|
50
|
+
```bash
|
|
51
|
+
npm install
|
|
52
|
+
```
|
|
53
|
+
3. Start the dev server:
|
|
54
|
+
```bash
|
|
55
|
+
npm run dev
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Note:** The templates are configured by default to connect to a local Pulse Relay running at `ws://localhost:4001`. Ensure your local Rust relay is running, or update `src/App.tsx` to point to a production relay URL.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
18
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
19
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
20
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
21
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
22
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
23
|
+
mod
|
|
24
|
+
));
|
|
25
|
+
|
|
26
|
+
// src/index.ts
|
|
27
|
+
var p = __toESM(require("@clack/prompts"));
|
|
28
|
+
var import_picocolors = __toESM(require("picocolors"));
|
|
29
|
+
var import_mri = __toESM(require("mri"));
|
|
30
|
+
var import_fs = __toESM(require("fs"));
|
|
31
|
+
var import_path = __toESM(require("path"));
|
|
32
|
+
var argv = (0, import_mri.default)(process.argv.slice(2));
|
|
33
|
+
var TEMPLATES_DIR = import_path.default.join(__dirname, "../templates");
|
|
34
|
+
async function main() {
|
|
35
|
+
p.intro(import_picocolors.default.bgMagenta(import_picocolors.default.white(" Create Pulse App ")));
|
|
36
|
+
let targetDir = argv._[0];
|
|
37
|
+
const project = await p.group(
|
|
38
|
+
{
|
|
39
|
+
path: () => {
|
|
40
|
+
if (targetDir) return Promise.resolve(targetDir);
|
|
41
|
+
return p.text({
|
|
42
|
+
message: "Project name:",
|
|
43
|
+
initialValue: "pulse-app",
|
|
44
|
+
validate: (val) => {
|
|
45
|
+
if (!val || val.trim() === "") return "Project name is required";
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
},
|
|
50
|
+
template: () => {
|
|
51
|
+
return p.select({
|
|
52
|
+
message: "Pick a template:",
|
|
53
|
+
options: [
|
|
54
|
+
{ value: "react-watch-together", label: "Watch Together (React + TS)", hint: "Synchronized video playback" },
|
|
55
|
+
{ value: "react-all-features", label: "All Features (React + TS)", hint: "Chat, Video, Audio, RPC" },
|
|
56
|
+
{ value: "vanilla-basic", label: "Vanilla JS (Basic)", hint: "Minimal setup" }
|
|
57
|
+
]
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
onCancel: () => {
|
|
63
|
+
p.cancel("Operation cancelled.");
|
|
64
|
+
process.exit(0);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
);
|
|
68
|
+
const destDir = import_path.default.resolve(process.cwd(), project.path);
|
|
69
|
+
if (!import_fs.default.existsSync(destDir)) {
|
|
70
|
+
import_fs.default.mkdirSync(destDir, { recursive: true });
|
|
71
|
+
}
|
|
72
|
+
const s = p.spinner();
|
|
73
|
+
s.start(`Scaffolding project in ${import_picocolors.default.cyan(project.path)}...`);
|
|
74
|
+
const templateDir = import_path.default.join(TEMPLATES_DIR, project.template);
|
|
75
|
+
if (!import_fs.default.existsSync(templateDir)) {
|
|
76
|
+
s.stop(import_picocolors.default.red(`Template ${project.template} not found at ${templateDir}`));
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
copyDir(templateDir, destDir);
|
|
80
|
+
const gitignorePath = import_path.default.join(destDir, "_gitignore");
|
|
81
|
+
if (import_fs.default.existsSync(gitignorePath)) {
|
|
82
|
+
import_fs.default.renameSync(gitignorePath, import_path.default.join(destDir, ".gitignore"));
|
|
83
|
+
}
|
|
84
|
+
const pkgPath = import_path.default.join(destDir, "package.json");
|
|
85
|
+
if (import_fs.default.existsSync(pkgPath)) {
|
|
86
|
+
const pkg = JSON.parse(import_fs.default.readFileSync(pkgPath, "utf-8"));
|
|
87
|
+
pkg.name = import_path.default.basename(destDir);
|
|
88
|
+
import_fs.default.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
|
|
89
|
+
}
|
|
90
|
+
s.stop(`Scaffolded ${import_picocolors.default.cyan(project.template)} template in ${import_picocolors.default.cyan(project.path)}`);
|
|
91
|
+
p.note(
|
|
92
|
+
`cd ${project.path}
|
|
93
|
+
npm install
|
|
94
|
+
npm run dev`,
|
|
95
|
+
"Next steps"
|
|
96
|
+
);
|
|
97
|
+
p.outro(import_picocolors.default.magenta("Pulse is ready! \u{1F680}"));
|
|
98
|
+
}
|
|
99
|
+
function copyDir(src, dest) {
|
|
100
|
+
const entries = import_fs.default.readdirSync(src, { withFileTypes: true });
|
|
101
|
+
for (const entry of entries) {
|
|
102
|
+
const srcPath = import_path.default.join(src, entry.name);
|
|
103
|
+
if (entry.name === "node_modules" || entry.name === "dist") continue;
|
|
104
|
+
const destPath = import_path.default.join(dest, entry.name);
|
|
105
|
+
if (entry.isDirectory()) {
|
|
106
|
+
if (!import_fs.default.existsSync(destPath)) {
|
|
107
|
+
import_fs.default.mkdirSync(destPath);
|
|
108
|
+
}
|
|
109
|
+
copyDir(srcPath, destPath);
|
|
110
|
+
} else {
|
|
111
|
+
import_fs.default.copyFileSync(srcPath, destPath);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
main().catch(console.error);
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sansavision/create-pulse",
|
|
3
|
+
"version": "0.1.0-alpha.1",
|
|
4
|
+
"description": "Scaffold a new Pulse application",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"create-pulse": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsup src/index.ts --format cjs --dts",
|
|
11
|
+
"dev": "tsup src/index.ts --format cjs --watch"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"pulse",
|
|
15
|
+
"create-pulse",
|
|
16
|
+
"scaffold",
|
|
17
|
+
"cli"
|
|
18
|
+
],
|
|
19
|
+
"author": "Sansa",
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@clack/prompts": "^0.7.0",
|
|
23
|
+
"mri": "^1.2.0",
|
|
24
|
+
"picocolors": "^1.0.0"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/mri": "^1.1.5",
|
|
28
|
+
"@types/node": "^20.0.0",
|
|
29
|
+
"tsup": "^8.0.2",
|
|
30
|
+
"typescript": "^5.0.0"
|
|
31
|
+
}
|
|
32
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import * as p from '@clack/prompts';
|
|
3
|
+
import pc from 'picocolors';
|
|
4
|
+
import mri from 'mri';
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
8
|
+
|
|
9
|
+
const argv = mri(process.argv.slice(2));
|
|
10
|
+
|
|
11
|
+
// In CommonJS, __dirname is available, but since we compile with tsup to CJS, we can just use __dirname.
|
|
12
|
+
const TEMPLATES_DIR = path.join(__dirname, '../templates');
|
|
13
|
+
|
|
14
|
+
async function main() {
|
|
15
|
+
p.intro(pc.bgMagenta(pc.white(' Create Pulse App ')));
|
|
16
|
+
|
|
17
|
+
let targetDir = argv._[0];
|
|
18
|
+
|
|
19
|
+
const project = await p.group(
|
|
20
|
+
{
|
|
21
|
+
path: () => {
|
|
22
|
+
if (targetDir) return Promise.resolve(targetDir);
|
|
23
|
+
return p.text({
|
|
24
|
+
message: 'Project name:',
|
|
25
|
+
initialValue: 'pulse-app',
|
|
26
|
+
validate: (val) => {
|
|
27
|
+
if (!val || val.trim() === '') return 'Project name is required';
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
},
|
|
32
|
+
template: () => {
|
|
33
|
+
return p.select({
|
|
34
|
+
message: 'Pick a template:',
|
|
35
|
+
options: [
|
|
36
|
+
{ value: 'react-watch-together', label: 'Watch Together (React + TS)', hint: 'Synchronized video playback' },
|
|
37
|
+
{ value: 'react-all-features', label: 'All Features (React + TS)', hint: 'Chat, Video, Audio, RPC' },
|
|
38
|
+
{ value: 'vanilla-basic', label: 'Vanilla JS (Basic)', hint: 'Minimal setup' }
|
|
39
|
+
]
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
onCancel: () => {
|
|
45
|
+
p.cancel('Operation cancelled.');
|
|
46
|
+
process.exit(0);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
const destDir = path.resolve(process.cwd(), project.path);
|
|
52
|
+
if (!fs.existsSync(destDir)) {
|
|
53
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const s = p.spinner();
|
|
57
|
+
s.start(`Scaffolding project in ${pc.cyan(project.path)}...`);
|
|
58
|
+
|
|
59
|
+
const templateDir = path.join(TEMPLATES_DIR, project.template as string);
|
|
60
|
+
|
|
61
|
+
if (!fs.existsSync(templateDir)) {
|
|
62
|
+
s.stop(pc.red(`Template ${project.template} not found at ${templateDir}`));
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
copyDir(templateDir, destDir);
|
|
67
|
+
|
|
68
|
+
// Rename _gitignore to .gitignore
|
|
69
|
+
const gitignorePath = path.join(destDir, '_gitignore');
|
|
70
|
+
if (fs.existsSync(gitignorePath)) {
|
|
71
|
+
fs.renameSync(gitignorePath, path.join(destDir, '.gitignore'));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Update package.json name to match the project path
|
|
75
|
+
const pkgPath = path.join(destDir, 'package.json');
|
|
76
|
+
if (fs.existsSync(pkgPath)) {
|
|
77
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
78
|
+
pkg.name = path.basename(destDir);
|
|
79
|
+
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
s.stop(`Scaffolded ${pc.cyan(project.template)} template in ${pc.cyan(project.path)}`);
|
|
83
|
+
|
|
84
|
+
p.note(
|
|
85
|
+
`cd ${project.path}\n` +
|
|
86
|
+
`npm install\n` +
|
|
87
|
+
`npm run dev`,
|
|
88
|
+
'Next steps'
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
p.outro(pc.magenta('Pulse is ready! 🚀'));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function copyDir(src: string, dest: string) {
|
|
95
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
96
|
+
|
|
97
|
+
for (const entry of entries) {
|
|
98
|
+
const srcPath = path.join(src, entry.name);
|
|
99
|
+
if (entry.name === 'node_modules' || entry.name === 'dist') continue;
|
|
100
|
+
|
|
101
|
+
const destPath = path.join(dest, entry.name);
|
|
102
|
+
|
|
103
|
+
if (entry.isDirectory()) {
|
|
104
|
+
if (!fs.existsSync(destPath)) {
|
|
105
|
+
fs.mkdirSync(destPath);
|
|
106
|
+
}
|
|
107
|
+
copyDir(srcPath, destPath);
|
|
108
|
+
} else {
|
|
109
|
+
fs.copyFileSync(srcPath, destPath);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en" class="dark">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Watch Together - Pulse Demo</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root"></div>
|
|
10
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|