@polderlabs/bizar 4.9.0 → 5.0.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/bizar-dash/dist/assets/icons-Bo0iH9EC.js +701 -0
- package/bizar-dash/dist/assets/icons-Bo0iH9EC.js.map +1 -0
- package/bizar-dash/dist/assets/{index-DU61awG3.js → index-DmpSFPJY.js} +1 -1
- package/bizar-dash/dist/assets/{index-DU61awG3.js.map → index-DmpSFPJY.js.map} +1 -1
- package/bizar-dash/dist/assets/main-C1cpttnv.js +19 -0
- package/bizar-dash/dist/assets/main-C1cpttnv.js.map +1 -0
- package/bizar-dash/dist/assets/main-DTkNlLrw.css +1 -0
- package/bizar-dash/dist/assets/markdown-DIquRulQ.js +29 -0
- package/bizar-dash/dist/assets/markdown-DIquRulQ.js.map +1 -0
- package/bizar-dash/dist/assets/mobile-BtxQJftK.js +2 -0
- package/bizar-dash/dist/assets/mobile-BtxQJftK.js.map +1 -0
- package/bizar-dash/dist/assets/mobile-DD-FZrTC.js +1 -0
- package/bizar-dash/dist/assets/{mobile-CL5uUQEC.js.map → mobile-DD-FZrTC.js.map} +1 -1
- package/bizar-dash/dist/assets/react-vendor-DZRUXSPQ.js +40 -0
- package/bizar-dash/dist/assets/react-vendor-DZRUXSPQ.js.map +1 -0
- package/bizar-dash/dist/index.html +6 -3
- package/bizar-dash/dist/mobile.html +5 -2
- package/bizar-dash/node_modules/.package-lock.json +6 -0
- package/bizar-dash/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/bizar-dash/package-lock.json +6 -0
- package/bizar-dash/skills/eval/SKILL.md +237 -0
- package/bizar-dash/src/server/api.mjs +35 -0
- package/bizar-dash/src/server/auth.mjs +155 -1
- package/bizar-dash/src/server/diagnostics-store.mjs +452 -2
- package/bizar-dash/src/server/eval-store.mjs +226 -0
- package/bizar-dash/src/server/eval.mjs +347 -0
- package/bizar-dash/src/server/memory-store.mjs +46 -0
- package/bizar-dash/src/server/ocr.mjs +55 -0
- package/bizar-dash/src/server/plugins/registry.mjs +363 -0
- package/bizar-dash/src/server/plugins/sandbox.mjs +655 -0
- package/bizar-dash/src/server/plugins/store.mjs +659 -0
- package/bizar-dash/src/server/providers-store.mjs +11 -4
- package/bizar-dash/src/server/routes/_shared.mjs +2 -2
- package/bizar-dash/src/server/routes/clipboard.mjs +173 -0
- package/bizar-dash/src/server/routes/doctor.mjs +71 -0
- package/bizar-dash/src/server/routes/env-vars.mjs +67 -1
- package/bizar-dash/src/server/routes/eval.mjs +147 -0
- package/bizar-dash/src/server/routes/memory.mjs +3 -0
- package/bizar-dash/src/server/routes/ocr.mjs +182 -0
- package/bizar-dash/src/server/routes/opencode-session-detail.mjs +61 -42
- package/bizar-dash/src/server/routes/plugins.mjs +220 -0
- package/bizar-dash/src/server/routes/schedules.mjs +55 -0
- package/bizar-dash/src/server/routes/users.mjs +84 -0
- package/bizar-dash/src/server/routes/voice.mjs +131 -0
- package/bizar-dash/src/server/routes/workspaces.mjs +204 -0
- package/bizar-dash/src/server/serve-info.mjs +172 -0
- package/bizar-dash/src/server/voice-store.mjs +202 -0
- package/bizar-dash/src/server/voice-transcribe.mjs +72 -0
- package/bizar-dash/src/server/workspaces.mjs +626 -0
- package/bizar-dash/src/web/App.tsx +48 -11
- package/bizar-dash/src/web/components/AutosaveField.tsx +50 -0
- package/bizar-dash/src/web/components/DoctorPanel.tsx +160 -0
- package/bizar-dash/src/web/components/EnvVarManager.tsx +245 -62
- package/bizar-dash/src/web/components/InviteDialog.tsx +205 -0
- package/bizar-dash/src/web/components/ScheduleTemplateCard.tsx +71 -0
- package/bizar-dash/src/web/components/ScreenshotCapture.tsx +138 -0
- package/bizar-dash/src/web/components/ScreenshotOCR.tsx +42 -0
- package/bizar-dash/src/web/components/SettingsNav.tsx +101 -0
- package/bizar-dash/src/web/components/Sidebar.tsx +25 -1
- package/bizar-dash/src/web/components/StatusBadge.tsx +33 -2
- package/bizar-dash/src/web/components/Topbar.tsx +11 -0
- package/bizar-dash/src/web/components/VoiceNotesPanel.tsx +182 -0
- package/bizar-dash/src/web/components/VoiceRecorder.tsx +104 -0
- package/bizar-dash/src/web/components/WorkspaceSelector.tsx +158 -0
- package/bizar-dash/src/web/components/chat/ChatInfoPanel.tsx +64 -0
- package/bizar-dash/src/web/components/chat/Composer.tsx +1 -1
- package/bizar-dash/src/web/components/chat/useChat.ts +118 -1
- package/bizar-dash/src/web/hooks/useAutosave.ts +107 -0
- package/bizar-dash/src/web/lib/types.ts +134 -0
- package/bizar-dash/src/web/styles/chat.css +8 -5
- package/bizar-dash/src/web/styles/main.css +77 -2
- package/bizar-dash/src/web/styles/memory.css +82 -0
- package/bizar-dash/src/web/styles/settings.css +265 -0
- package/bizar-dash/src/web/views/Chat.tsx +15 -1
- package/bizar-dash/src/web/views/Doctor.tsx +317 -0
- package/bizar-dash/src/web/views/Memory.tsx +17 -2
- package/bizar-dash/src/web/views/MiniMaxUsage.tsx +1 -0
- package/bizar-dash/src/web/views/Schedules.tsx +94 -12
- package/bizar-dash/src/web/views/Settings.tsx +78 -45
- package/bizar-dash/src/web/views/Workspace.tsx +294 -0
- package/bizar-dash/src/web/views/memory/ConfigPanel.tsx +114 -133
- package/bizar-dash/src/web/views/memory/FromScreenshotPanel.tsx +23 -0
- package/bizar-dash/src/web/views/memory/VaultFromClipboardPanel.tsx +101 -0
- package/bizar-dash/src/web/views/settings/AgentSection.tsx +23 -12
- package/bizar-dash/src/web/views/settings/EnvVarsSection.tsx +5 -6
- package/bizar-dash/src/web/views/settings/GeneralSection.tsx +38 -15
- package/bizar-dash/src/web/views/settings/MemorySection.tsx +92 -8
- package/bizar-dash/src/web/views/settings/WorkspacesSection.tsx +189 -0
- package/bizar-dash/tests/autosave.test.tsx +276 -0
- package/bizar-dash/tests/bundle-analysis.test.mjs +5 -2
- package/bizar-dash/tests/chat-composer.test.tsx +140 -0
- package/bizar-dash/tests/clipboard.test.mjs +147 -0
- package/bizar-dash/tests/components/doctor-panel.test.tsx +105 -0
- package/bizar-dash/tests/components/screenshot-ocr.test.tsx +75 -0
- package/bizar-dash/tests/components/workspace-selector.test.tsx +73 -0
- package/bizar-dash/tests/deploy-templates.test.mjs +100 -0
- package/bizar-dash/tests/diagnostics-store.test.mjs +206 -0
- package/bizar-dash/tests/env-vars-extended.test.mjs +190 -0
- package/bizar-dash/tests/eval/fixtures.test.mjs +141 -0
- package/bizar-dash/tests/eval/report.test.mjs +284 -0
- package/bizar-dash/tests/eval/runner.test.mjs +471 -0
- package/bizar-dash/tests/layout-spacing.test.mjs +101 -0
- package/bizar-dash/tests/memory-default-vault.test.mjs +98 -0
- package/bizar-dash/tests/minimax-bar.test.tsx +113 -0
- package/bizar-dash/tests/minimax-models.test.mjs +123 -0
- package/bizar-dash/tests/ocr.test.mjs +87 -0
- package/bizar-dash/tests/opencode-sessions-detail.test.mjs +158 -0
- package/bizar-dash/tests/plugins-registry.test.mjs +387 -0
- package/bizar-dash/tests/plugins-sandbox.test.mjs +374 -0
- package/bizar-dash/tests/plugins-store.test.mjs +455 -0
- package/bizar-dash/tests/routes-doctor.test.mjs +159 -0
- package/bizar-dash/tests/schedules-templates.test.mjs +144 -0
- package/bizar-dash/tests/settings-layout.test.tsx +129 -0
- package/bizar-dash/tests/settings-mode-wiring.test.tsx +151 -0
- package/bizar-dash/tests/settings-nav.test.tsx +126 -0
- package/bizar-dash/tests/users.test.mjs +108 -0
- package/bizar-dash/tests/voice-recorder.test.tsx +95 -0
- package/bizar-dash/tests/voice-store.test.mjs +148 -0
- package/bizar-dash/tests/voice-transcribe.test.mjs +87 -0
- package/bizar-dash/tests/workspaces.test.mjs +527 -0
- package/cli/bin.mjs +72 -2
- package/cli/commands/clip.mjs +146 -0
- package/cli/commands/deploy/cloudflare.mjs +250 -0
- package/cli/commands/deploy/docker.mjs +221 -0
- package/cli/commands/deploy/fly.mjs +161 -0
- package/cli/commands/deploy/vercel.mjs +225 -0
- package/cli/commands/deploy.mjs +240 -0
- package/cli/commands/eval.mjs +378 -0
- package/cli/commands/marketplace.mjs +64 -0
- package/cli/commands/minimax.mjs +5 -2
- package/cli/commands/ocr.mjs +165 -0
- package/cli/commands/plugin.mjs +358 -0
- package/cli/commands/voice.mjs +211 -0
- package/cli/commands/workspace.mjs +247 -0
- package/cli/tests/minimax-cli.test.mjs +79 -0
- package/config/agents/frigg.md +1 -1
- package/config/agents/heimdall.md +1 -1
- package/config/agents/mimir.md +1 -1
- package/config/agents/quick.md +1 -1
- package/config/agents/semble-search.md +1 -1
- package/config/agents/vor.md +1 -1
- package/config/opencode.json +28 -21
- package/config/opencode.json.template +8 -1
- package/package.json +12 -8
- package/plugins/bizar/index.ts +77 -0
- package/plugins/bizar/src/compaction.d.mts +48 -0
- package/plugins/bizar/src/compaction.mjs +192 -0
- package/plugins/bizar/tests/compaction.test.ts +264 -0
- package/templates/deploy/cloudflare/README.md +32 -0
- package/templates/deploy/cloudflare/functions-index.template.js +15 -0
- package/templates/deploy/cloudflare/wrangler.toml.template +9 -0
- package/templates/deploy/docker/.env.template +16 -0
- package/templates/deploy/docker/README.md +58 -0
- package/templates/deploy/docker/docker-compose.template.yml +23 -0
- package/templates/deploy/fly/README.md +35 -0
- package/templates/deploy/fly/fly.toml.template +28 -0
- package/templates/deploy/vercel/README.md +29 -0
- package/templates/deploy/vercel/api-index.template.js +18 -0
- package/templates/deploy/vercel/vercel.json.template +16 -0
- package/templates/eval-fixtures/README.md +58 -0
- package/templates/eval-fixtures/code-search-basic.json +28 -0
- package/templates/eval-fixtures/latency-bounds.json +16 -0
- package/templates/eval-fixtures/regression-suite.json +79 -0
- package/templates/eval-fixtures/response-format.json +30 -0
- package/templates/eval-fixtures/tool-call-correctness.json +24 -0
- package/templates/plugin-template/README.md +121 -0
- package/templates/plugin-template/index.js +66 -0
- package/templates/plugin-template/plugin.json +42 -0
- package/templates/plugin-template/tests/plugin.test.js +83 -0
- package/templates/schedules/daily-backup.json +12 -0
- package/templates/schedules/daily-cleanup.json +12 -0
- package/templates/schedules/hourly-health-check.json +12 -0
- package/templates/schedules/webhook-on-push.json +13 -0
- package/templates/schedules/weekly-digest.json +13 -0
- package/bizar-dash/dist/assets/main-DaC1Lc6q.js +0 -366
- package/bizar-dash/dist/assets/main-DaC1Lc6q.js.map +0 -1
- package/bizar-dash/dist/assets/main-DfmIfOUS.css +0 -1
- package/bizar-dash/dist/assets/mobile-CL5uUQEC.js +0 -1
- package/bizar-dash/dist/assets/mobile-D5WTWvuh.js +0 -338
- package/bizar-dash/dist/assets/mobile-D5WTWvuh.js.map +0 -1
- package/bizar-dash/src/web/views/settings/BackupSection.tsx +0 -16
- package/bizar-dash/src/web/views/settings/ProvidersSection.tsx +0 -16
- package/bizar-dash/src/web/views/settings/SkillsSection.tsx +0 -16
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cli/commands/deploy/fly.mjs
|
|
3
|
+
*
|
|
4
|
+
* Fly.io deployment via flyctl CLI.
|
|
5
|
+
*
|
|
6
|
+
* Generates fly.toml, optionally uses existing Dockerfile (v4.9),
|
|
7
|
+
* then runs `flyctl deploy`.
|
|
8
|
+
*/
|
|
9
|
+
import chalk from 'chalk';
|
|
10
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'node:fs';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
import { execSync } from 'node:child_process';
|
|
13
|
+
|
|
14
|
+
// ── Scaffold generation ──────────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
function generateScaffold(outDir, opts) {
|
|
17
|
+
const scaffoldRoot = join(outDir, 'fly');
|
|
18
|
+
|
|
19
|
+
if (!existsSync(scaffoldRoot)) {
|
|
20
|
+
mkdirSync(scaffoldRoot, { recursive: true });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// fly.toml
|
|
24
|
+
const flyToml = [
|
|
25
|
+
'# fly.toml — Bizar dashboard',
|
|
26
|
+
'# Generated by `bizar deploy --to fly`',
|
|
27
|
+
'',
|
|
28
|
+
'app = "' + (opts.appName || 'bizar-dash') + '"',
|
|
29
|
+
'',
|
|
30
|
+
'primary_region = "' + (opts.region || 'iad') + '"',
|
|
31
|
+
'',
|
|
32
|
+
'[build]',
|
|
33
|
+
' dockerfile = "../Dockerfile"',
|
|
34
|
+
'',
|
|
35
|
+
'[http_service]',
|
|
36
|
+
' internal_port = 4097',
|
|
37
|
+
' force_https = true',
|
|
38
|
+
' auto_stop_machines = true',
|
|
39
|
+
' auto_start_machines = true',
|
|
40
|
+
' min_machines_running = 0',
|
|
41
|
+
'',
|
|
42
|
+
'[[vm]]',
|
|
43
|
+
' memory = "512mb"',
|
|
44
|
+
' cpu_kind = "shared"',
|
|
45
|
+
' cpus = 1',
|
|
46
|
+
'',
|
|
47
|
+
'[env]',
|
|
48
|
+
' BIZAR_DASHBOARD_PORT = "4097"',
|
|
49
|
+
' BIZAR_DASHBOARD_HOST = "0.0.0.0"',
|
|
50
|
+
' NODE_ENV = "production"',
|
|
51
|
+
'',
|
|
52
|
+
'[deploy]',
|
|
53
|
+
' strategy = "rolling"',
|
|
54
|
+
'',
|
|
55
|
+
].join('\n');
|
|
56
|
+
writeFileSync(join(scaffoldRoot, 'fly.toml'), flyToml);
|
|
57
|
+
|
|
58
|
+
console.log(chalk.dim(' \u2713 Scaffold generated at', scaffoldRoot));
|
|
59
|
+
return scaffoldRoot;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ── Deployment ───────────────────────────────────────────────────────────────
|
|
63
|
+
|
|
64
|
+
export async function deployToFly({ token, appName, region, projectRoot, outDir }) {
|
|
65
|
+
// Check that flyctl is installed
|
|
66
|
+
const flyctlPath = checkFlyctl();
|
|
67
|
+
if (!flyctlPath) {
|
|
68
|
+
throw new Error(
|
|
69
|
+
'flyctl not found. Install it first:\n' +
|
|
70
|
+
' curl -L https://fly.io/install.sh | sh\n' +
|
|
71
|
+
' Or download from https://fly.io/docs/getting-started/installing-flyctl/',
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Check Dockerfile exists (v4.9 work)
|
|
76
|
+
const dockerfilePath = join(projectRoot, 'Dockerfile');
|
|
77
|
+
if (!existsSync(dockerfilePath)) {
|
|
78
|
+
console.log(chalk.yellow(' \u26a0 No Dockerfile found at project root. Fly.io requires one.'));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// 1. Generate scaffold
|
|
82
|
+
const scaffoldRoot = generateScaffold(outDir, { appName, region });
|
|
83
|
+
const name = appName || 'bizar-dash';
|
|
84
|
+
|
|
85
|
+
// 2. Check if app exists; if not, launch it
|
|
86
|
+
try {
|
|
87
|
+
execSync(flyctlPath + ' apps list', {
|
|
88
|
+
encoding: 'utf8',
|
|
89
|
+
stdio: 'pipe',
|
|
90
|
+
timeout: 15000,
|
|
91
|
+
});
|
|
92
|
+
} catch {
|
|
93
|
+
console.log(chalk.dim(' App not found. Launching new app\u2026'));
|
|
94
|
+
try {
|
|
95
|
+
execSync(flyctlPath + ' launch --name "' + name + '" --region "' + (region || 'iad') + '" --no-deploy --now', {
|
|
96
|
+
cwd: projectRoot,
|
|
97
|
+
encoding: 'utf8',
|
|
98
|
+
stdio: 'pipe',
|
|
99
|
+
timeout: 60000,
|
|
100
|
+
});
|
|
101
|
+
} catch (err) {
|
|
102
|
+
throw new Error('flyctl launch failed: ' + err.message);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// 3. Check auth
|
|
107
|
+
if (!token) {
|
|
108
|
+
// flyctl uses its own auth — the --token flag may not be needed for flyctl
|
|
109
|
+
console.log(chalk.dim(' No FLY_API_TOKEN set. flyctl will use its own auth.'));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// 4. Deploy
|
|
113
|
+
console.log(chalk.dim(' Deploying to Fly.io\u2026'));
|
|
114
|
+
const deployCmd = flyctlPath + ' deploy' +
|
|
115
|
+
' --app "' + name + '"' +
|
|
116
|
+
' --strategy rolling' +
|
|
117
|
+
(token ? ' --access-token "' + token + '"' : '') +
|
|
118
|
+
' --remote-only';
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
execSync(deployCmd, {
|
|
122
|
+
cwd: projectRoot,
|
|
123
|
+
encoding: 'utf8',
|
|
124
|
+
stdio: 'pipe',
|
|
125
|
+
timeout: 300000, // 5 minutes for build + deploy
|
|
126
|
+
});
|
|
127
|
+
} catch (err) {
|
|
128
|
+
throw new Error('flyctl deploy failed: ' + err.message);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// 5. Get the app URL
|
|
132
|
+
const appUrl = 'https://' + name + '.fly.dev';
|
|
133
|
+
return {
|
|
134
|
+
url: appUrl,
|
|
135
|
+
deploymentId: name,
|
|
136
|
+
notes: [
|
|
137
|
+
'Region: ' + (region || 'iad'),
|
|
138
|
+
'Run `flyctl status --app ' + name + '` for details.',
|
|
139
|
+
],
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
144
|
+
|
|
145
|
+
function checkFlyctl() {
|
|
146
|
+
// Check common locations
|
|
147
|
+
const candidates = ['flyctl', 'fly'];
|
|
148
|
+
for (const cmd of candidates) {
|
|
149
|
+
try {
|
|
150
|
+
const result = execSync('which ' + cmd, {
|
|
151
|
+
encoding: 'utf8',
|
|
152
|
+
stdio: 'pipe',
|
|
153
|
+
timeout: 3000,
|
|
154
|
+
});
|
|
155
|
+
if (result.trim()) return cmd;
|
|
156
|
+
} catch {
|
|
157
|
+
// try next
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cli/commands/deploy/vercel.mjs
|
|
3
|
+
*
|
|
4
|
+
* Vercel deployment via REST API.
|
|
5
|
+
*
|
|
6
|
+
* Generates vercel.json + api/index.js (serverless function entry) +
|
|
7
|
+
* public/ (static dashboard bundle), then POSTs to Vercel Deployments API.
|
|
8
|
+
*/
|
|
9
|
+
import chalk from 'chalk';
|
|
10
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
import { execSync } from 'node:child_process';
|
|
13
|
+
|
|
14
|
+
const API_BASE = 'https://api.vercel.com';
|
|
15
|
+
|
|
16
|
+
// ── Scaffold generation ──────────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
function generateScaffold(outDir, opts) {
|
|
19
|
+
const scaffoldRoot = join(outDir, 'vercel');
|
|
20
|
+
|
|
21
|
+
if (!existsSync(scaffoldRoot)) {
|
|
22
|
+
mkdirSync(scaffoldRoot, { recursive: true });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// vercel.json
|
|
26
|
+
const vercelJson = {
|
|
27
|
+
name: opts.projectName || 'bizar-dash',
|
|
28
|
+
version: 2,
|
|
29
|
+
buildCommand: 'npm run build',
|
|
30
|
+
outputDirectory: 'public',
|
|
31
|
+
routes: [
|
|
32
|
+
{ src: '/api/(.*)', dest: '/api/index.js' },
|
|
33
|
+
{ src: '/(.*)', dest: '/public/$1' },
|
|
34
|
+
],
|
|
35
|
+
functions: {
|
|
36
|
+
'api/index.js': {
|
|
37
|
+
memory: 256,
|
|
38
|
+
maxDuration: 30,
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
writeFileSync(join(scaffoldRoot, 'vercel.json'), JSON.stringify(vercelJson, null, 2));
|
|
43
|
+
|
|
44
|
+
// api/index.js — serverless entry that serves the dashboard
|
|
45
|
+
const apiDir = join(scaffoldRoot, 'api');
|
|
46
|
+
if (!existsSync(apiDir)) mkdirSync(apiDir, { recursive: true });
|
|
47
|
+
|
|
48
|
+
const apiIndexContent = [
|
|
49
|
+
'// Vercel serverless entry — Bizar dashboard',
|
|
50
|
+
'// Generated by `bizar deploy --to vercel`',
|
|
51
|
+
"import express from 'express';",
|
|
52
|
+
"import { join, dirname } from 'node:path';",
|
|
53
|
+
"import { fileURLToPath } from 'node:url';",
|
|
54
|
+
'',
|
|
55
|
+
'const __dirname = dirname(fileURLToPath(import.meta.url));',
|
|
56
|
+
'const app = express();',
|
|
57
|
+
'',
|
|
58
|
+
'// Serve static files from the dashboard build',
|
|
59
|
+
"app.use(express.static(join(__dirname, '..', 'public')));",
|
|
60
|
+
'',
|
|
61
|
+
'// Fallback to index.html for SPA routing',
|
|
62
|
+
"app.get('*', (_req, res) => {",
|
|
63
|
+
" res.sendFile(join(__dirname, '..', 'public', 'index.html'));",
|
|
64
|
+
'});',
|
|
65
|
+
'',
|
|
66
|
+
'export default app;',
|
|
67
|
+
'',
|
|
68
|
+
].join('\n');
|
|
69
|
+
writeFileSync(join(apiDir, 'index.js'), apiIndexContent);
|
|
70
|
+
|
|
71
|
+
// Build the dashboard bundle into public/
|
|
72
|
+
const publicDir = join(scaffoldRoot, 'public');
|
|
73
|
+
if (!existsSync(publicDir)) mkdirSync(publicDir, { recursive: true });
|
|
74
|
+
|
|
75
|
+
console.log(chalk.dim(' Building dashboard bundle\u2026'));
|
|
76
|
+
try {
|
|
77
|
+
execSync('npm run build', {
|
|
78
|
+
cwd: opts.projectRoot,
|
|
79
|
+
encoding: 'utf8',
|
|
80
|
+
stdio: 'pipe',
|
|
81
|
+
timeout: 120000,
|
|
82
|
+
});
|
|
83
|
+
} catch (err) {
|
|
84
|
+
throw new Error('Dashboard build failed: ' + err.message);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Copy built assets into public/
|
|
88
|
+
const distDir = join(opts.projectRoot, 'dist');
|
|
89
|
+
if (existsSync(distDir)) {
|
|
90
|
+
execSync('cp -r "' + distDir + '"/* "' + publicDir + '/"', { stdio: 'pipe' });
|
|
91
|
+
} else {
|
|
92
|
+
console.log(chalk.yellow(' \u26a0 No dist/ found \u2014 deploying scaffold only'));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
console.log(chalk.dim(' \u2713 Scaffold generated at', scaffoldRoot));
|
|
96
|
+
return scaffoldRoot;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ── API helpers ──────────────────────────────────────────────────────────────
|
|
100
|
+
|
|
101
|
+
async function vercelPost(path, token, body) {
|
|
102
|
+
const url = API_BASE + path;
|
|
103
|
+
const res = await fetch(url, {
|
|
104
|
+
method: 'POST',
|
|
105
|
+
headers: {
|
|
106
|
+
Authorization: 'Bearer ' + token,
|
|
107
|
+
'Content-Type': 'application/json',
|
|
108
|
+
},
|
|
109
|
+
body: JSON.stringify(body),
|
|
110
|
+
});
|
|
111
|
+
const text = await res.text();
|
|
112
|
+
let json = null;
|
|
113
|
+
try { json = text ? JSON.parse(text) : null; } catch { /* keep raw */ }
|
|
114
|
+
if (!res.ok) {
|
|
115
|
+
const msg = json?.error?.message || json?.error?.code || text || 'HTTP ' + res.status;
|
|
116
|
+
throw new Error('Vercel API error: ' + msg);
|
|
117
|
+
}
|
|
118
|
+
return json;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function vercelGet(path, token) {
|
|
122
|
+
const url = API_BASE + path;
|
|
123
|
+
const res = await fetch(url, {
|
|
124
|
+
headers: { Authorization: 'Bearer ' + token },
|
|
125
|
+
});
|
|
126
|
+
const text = await res.text();
|
|
127
|
+
let json = null;
|
|
128
|
+
try { json = text ? JSON.parse(text) : null; } catch { /* keep raw */ }
|
|
129
|
+
if (!res.ok) {
|
|
130
|
+
const msg = json?.error?.message || json?.error?.code || text || 'HTTP ' + res.status;
|
|
131
|
+
throw new Error('Vercel API error: ' + msg);
|
|
132
|
+
}
|
|
133
|
+
return json;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ── Deployment ───────────────────────────────────────────────────────────────
|
|
137
|
+
|
|
138
|
+
export async function deployToVercel({ token, projectName, projectRoot, outDir }) {
|
|
139
|
+
if (!token) {
|
|
140
|
+
throw new Error('Vercel token required. Pass --token or set VERCEL_TOKEN env var.');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// 1. Generate scaffold
|
|
144
|
+
const scaffoldRoot = generateScaffold(outDir, { projectName, projectRoot });
|
|
145
|
+
const name = projectName || 'bizar-dash';
|
|
146
|
+
|
|
147
|
+
// 2. Upload to Vercel
|
|
148
|
+
console.log(chalk.dim(' Uploading to Vercel\u2026'));
|
|
149
|
+
|
|
150
|
+
const body = {
|
|
151
|
+
name,
|
|
152
|
+
deploymentType: 'instant',
|
|
153
|
+
files: collectFiles(scaffoldRoot),
|
|
154
|
+
projectSettings: {
|
|
155
|
+
framework: 'other',
|
|
156
|
+
installCommand: 'npm install',
|
|
157
|
+
buildCommand: 'npm run build',
|
|
158
|
+
outputDirectory: 'public',
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const result = await vercelPost('/v13/deployments', token, body);
|
|
163
|
+
|
|
164
|
+
// 3. Poll for ready state
|
|
165
|
+
const deploymentId = result.id || result.uid;
|
|
166
|
+
if (!deploymentId) {
|
|
167
|
+
throw new Error('No deployment ID returned from Vercel');
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
console.log(chalk.dim(' Deployment ID:', deploymentId));
|
|
171
|
+
|
|
172
|
+
// Poll up to 60 seconds
|
|
173
|
+
for (let i = 0; i < 30; i++) {
|
|
174
|
+
const status = await vercelGet('/v13/deployments/' + deploymentId, token);
|
|
175
|
+
const state = status.readyState || status.state;
|
|
176
|
+
if (state === 'READY') {
|
|
177
|
+
const url = status.url || 'https://' + deploymentId + '.vercel.app';
|
|
178
|
+
return {
|
|
179
|
+
url: 'https://' + url,
|
|
180
|
+
deploymentId,
|
|
181
|
+
notes: ['Open the URL in your browser to view your dashboard'],
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
if (state === 'ERROR' || state === 'CANCELED') {
|
|
185
|
+
throw new Error('Deployment failed with state: ' + state);
|
|
186
|
+
}
|
|
187
|
+
// Wait 2 seconds before polling again
|
|
188
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Timeout — return whatever we have
|
|
192
|
+
return {
|
|
193
|
+
url: 'https://' + deploymentId + '.vercel.app',
|
|
194
|
+
deploymentId,
|
|
195
|
+
notes: ['Deployment is still building. Check the Vercel dashboard for status.'],
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Collect files from a directory for the Vercel API.
|
|
203
|
+
* Returns an array of { file, data } objects (base64-encoded).
|
|
204
|
+
*/
|
|
205
|
+
function collectFiles(rootDir) {
|
|
206
|
+
const files = [];
|
|
207
|
+
|
|
208
|
+
function walk(dir, prefix) {
|
|
209
|
+
const entries = readdirSync(dir);
|
|
210
|
+
for (const entry of entries) {
|
|
211
|
+
const full = join(dir, entry);
|
|
212
|
+
const rel = prefix ? prefix + '/' + entry : entry;
|
|
213
|
+
const stat = statSync(full);
|
|
214
|
+
if (stat.isDirectory()) {
|
|
215
|
+
walk(full, rel);
|
|
216
|
+
} else {
|
|
217
|
+
const content = readFileSync(full, 'base64');
|
|
218
|
+
files.push({ file: rel, data: content, encoding: 'base64' });
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
walk(rootDir, '');
|
|
224
|
+
return files;
|
|
225
|
+
}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cli/commands/deploy.mjs
|
|
3
|
+
*
|
|
4
|
+
* v5.0 — One-click deploy to Vercel, Cloudflare, Fly.io, or Docker registry.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* bizar deploy --to vercel [--token <token>] [--project-name <name>]
|
|
8
|
+
* bizar deploy --to cloudflare [--token <token>] [--project-name <name>]
|
|
9
|
+
* bizar deploy --to fly [--token <token>] [--app-name <name>] [--region <region>]
|
|
10
|
+
* bizar deploy --to docker [--registry <ghcr.io|docker.io|...>] [--image-name <name>]
|
|
11
|
+
* bizar deploy --to docker --compose
|
|
12
|
+
*/
|
|
13
|
+
import chalk from 'chalk';
|
|
14
|
+
import { existsSync } from 'node:fs';
|
|
15
|
+
import { join } from 'node:path';
|
|
16
|
+
import { fileURLToPath } from 'node:url';
|
|
17
|
+
|
|
18
|
+
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
|
19
|
+
const PROJECT_ROOT = join(__dirname, '..', '..');
|
|
20
|
+
|
|
21
|
+
// ── Help text ──────────────────────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
export function showDeployHelp() {
|
|
24
|
+
console.log(`
|
|
25
|
+
bizar deploy — One-click deploy to your hosting platform of choice
|
|
26
|
+
|
|
27
|
+
Usage:
|
|
28
|
+
bizar deploy --to <platform> [options]
|
|
29
|
+
|
|
30
|
+
Platforms:
|
|
31
|
+
vercel Deploy to Vercel (serverless)
|
|
32
|
+
cloudflare Deploy to Cloudflare Pages + Workers
|
|
33
|
+
fly Deploy to Fly.io
|
|
34
|
+
docker Build & push to a Docker registry (ghcr.io, docker.io, …)
|
|
35
|
+
|
|
36
|
+
Options:
|
|
37
|
+
--to <platform> Required. Target platform.
|
|
38
|
+
--token <token> API token for the platform.
|
|
39
|
+
--project-name <name> Project / site name (Vercel, Cloudflare).
|
|
40
|
+
--app-name <name> Fly.io app name.
|
|
41
|
+
--region <region> Fly.io region (default: iad).
|
|
42
|
+
--registry <url> Docker registry URL (default: ghcr.io).
|
|
43
|
+
--image-name <name> Docker image name.
|
|
44
|
+
--compose Generate docker-compose.yml + .env template.
|
|
45
|
+
--out-dir <path> Output directory for scaffold files
|
|
46
|
+
(default: ./bizar-deploy/).
|
|
47
|
+
--help Show this help.
|
|
48
|
+
|
|
49
|
+
Environment variables (fallback when --token is omitted):
|
|
50
|
+
VERCEL_TOKEN Vercel API token
|
|
51
|
+
CLOUDFLARE_API_TOKEN Cloudflare API token
|
|
52
|
+
FLY_API_TOKEN Fly.io API token
|
|
53
|
+
|
|
54
|
+
Examples:
|
|
55
|
+
bizar deploy --to vercel --token \$(cat ~/.vercel/token)
|
|
56
|
+
bizar deploy --to cloudflare --project-name my-dash
|
|
57
|
+
bizar deploy --to fly --app-name bizar-dash --region lhr
|
|
58
|
+
bizar deploy --to docker --registry ghcr.io/my-org --image-name bizar-dash
|
|
59
|
+
bizar deploy --to docker --compose
|
|
60
|
+
`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ── Option parsing ────────────────────────────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
function parseDeployOpts(args) {
|
|
66
|
+
const opts = {
|
|
67
|
+
to: null,
|
|
68
|
+
token: null,
|
|
69
|
+
projectName: null,
|
|
70
|
+
appName: null,
|
|
71
|
+
region: null,
|
|
72
|
+
registry: null,
|
|
73
|
+
imageName: null,
|
|
74
|
+
compose: false,
|
|
75
|
+
outDir: null,
|
|
76
|
+
help: false,
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
for (let i = 0; i < args.length; i++) {
|
|
80
|
+
const a = args[i];
|
|
81
|
+
switch (a) {
|
|
82
|
+
case '--to':
|
|
83
|
+
opts.to = args[++i];
|
|
84
|
+
break;
|
|
85
|
+
case '--token':
|
|
86
|
+
opts.token = args[++i];
|
|
87
|
+
break;
|
|
88
|
+
case '--project-name':
|
|
89
|
+
opts.projectName = args[++i];
|
|
90
|
+
break;
|
|
91
|
+
case '--app-name':
|
|
92
|
+
opts.appName = args[++i];
|
|
93
|
+
break;
|
|
94
|
+
case '--region':
|
|
95
|
+
opts.region = args[++i];
|
|
96
|
+
break;
|
|
97
|
+
case '--registry':
|
|
98
|
+
opts.registry = args[++i];
|
|
99
|
+
break;
|
|
100
|
+
case '--image-name':
|
|
101
|
+
opts.imageName = args[++i];
|
|
102
|
+
break;
|
|
103
|
+
case '--out-dir':
|
|
104
|
+
opts.outDir = args[++i];
|
|
105
|
+
break;
|
|
106
|
+
case '--compose':
|
|
107
|
+
opts.compose = true;
|
|
108
|
+
break;
|
|
109
|
+
case '--help':
|
|
110
|
+
case '-h':
|
|
111
|
+
opts.help = true;
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return opts;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ── Prerequisite checks ──────────────────────────────────────────────────────
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Check that required CLI tools are on PATH.
|
|
123
|
+
* Returns array of missing tool names.
|
|
124
|
+
*/
|
|
125
|
+
function checkPrerequisites(required) {
|
|
126
|
+
const { execFileSync } = require_node('child_process');
|
|
127
|
+
const missing = [];
|
|
128
|
+
for (const tool of required) {
|
|
129
|
+
try {
|
|
130
|
+
execFileSync('which', [tool], { encoding: 'utf8', timeout: 3000, stdio: 'pipe' });
|
|
131
|
+
} catch {
|
|
132
|
+
missing.push(tool);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return missing;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function require_node(mod) {
|
|
139
|
+
// eslint-disable-next-line no-new-func
|
|
140
|
+
return Function('return require("' + mod + '")')();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ── Main dispatch ─────────────────────────────────────────────────────────────
|
|
144
|
+
|
|
145
|
+
export async function runDeploy(deployArgs) {
|
|
146
|
+
const opts = parseDeployOpts(deployArgs);
|
|
147
|
+
|
|
148
|
+
if (opts.help || !opts.to) {
|
|
149
|
+
showDeployHelp();
|
|
150
|
+
if (!opts.to) {
|
|
151
|
+
console.error(chalk.red(' ✗ --to <platform> is required.'));
|
|
152
|
+
process.exit(1);
|
|
153
|
+
}
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const platform = opts.to.toLowerCase();
|
|
158
|
+
const outDir = opts.outDir || join(process.cwd(), 'bizar-deploy');
|
|
159
|
+
|
|
160
|
+
// Resolve tokens from env if not provided
|
|
161
|
+
if (!opts.token) {
|
|
162
|
+
const envMap = {
|
|
163
|
+
vercel: 'VERCEL_TOKEN',
|
|
164
|
+
cloudflare: 'CLOUDFLARE_API_TOKEN',
|
|
165
|
+
fly: 'FLY_API_TOKEN',
|
|
166
|
+
};
|
|
167
|
+
const envVar = envMap[platform];
|
|
168
|
+
if (envVar && process.env[envVar]) {
|
|
169
|
+
opts.token = process.env[envVar];
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const context = {
|
|
174
|
+
...opts,
|
|
175
|
+
projectRoot: PROJECT_ROOT,
|
|
176
|
+
outDir,
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
switch (platform) {
|
|
181
|
+
case 'vercel': {
|
|
182
|
+
const { deployToVercel } = await import('./deploy/vercel.mjs');
|
|
183
|
+
const result = await deployToVercel(context);
|
|
184
|
+
printResult('Vercel', result);
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
case 'cloudflare': {
|
|
188
|
+
const { deployToCloudflare } = await import('./deploy/cloudflare.mjs');
|
|
189
|
+
const result = await deployToCloudflare(context);
|
|
190
|
+
printResult('Cloudflare', result);
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
case 'fly': {
|
|
194
|
+
const { deployToFly } = await import('./deploy/fly.mjs');
|
|
195
|
+
const result = await deployToFly(context);
|
|
196
|
+
printResult('Fly.io', result);
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
case 'docker': {
|
|
200
|
+
const { deployToDocker } = await import('./deploy/docker.mjs');
|
|
201
|
+
const result = await deployToDocker(context);
|
|
202
|
+
printResult('Docker', result);
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
default:
|
|
206
|
+
console.error(chalk.red(` ✗ Unknown platform: ${platform}`));
|
|
207
|
+
showDeployHelp();
|
|
208
|
+
process.exit(1);
|
|
209
|
+
}
|
|
210
|
+
} catch (err) {
|
|
211
|
+
console.error(chalk.red(` ✗ Deploy failed: ${err.message}`));
|
|
212
|
+
if (err.stderr) {
|
|
213
|
+
console.error(chalk.dim(err.stderr));
|
|
214
|
+
}
|
|
215
|
+
process.exit(1);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function printResult(platformName, result) {
|
|
220
|
+
console.log(chalk.green(` ✓ Deployed to ${platformName}`));
|
|
221
|
+
if (result.url) {
|
|
222
|
+
console.log(` URL: ${chalk.cyan(result.url)}`);
|
|
223
|
+
}
|
|
224
|
+
if (result.deploymentId) {
|
|
225
|
+
console.log(` ID: ${chalk.dim(result.deploymentId)}`);
|
|
226
|
+
}
|
|
227
|
+
if (result.notes && result.notes.length > 0) {
|
|
228
|
+
for (const note of result.notes) {
|
|
229
|
+
console.log(chalk.dim(` ${note}`));
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export async function run(name, args, isHelpRequest) {
|
|
235
|
+
if (args.length === 0 || isHelpRequest) {
|
|
236
|
+
showDeployHelp();
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
await runDeploy(args);
|
|
240
|
+
}
|