deploy.sh 2.0.0 → 3.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/.claude/settings.local.json +36 -0
- package/.github/ISSUE_TEMPLATE/bug_report.yml +105 -0
- package/.github/ISSUE_TEMPLATE/config.yml +5 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +56 -0
- package/.github/workflows/ci.yml +15 -34
- package/.github/workflows/pages.yml +48 -0
- package/.oxfmtrc.json +7 -0
- package/.oxlintrc.json +11 -0
- package/LICENSE +183 -183
- package/README.md +99 -10
- package/app/actions/deployments.ts +82 -0
- package/app/actions/metrics.ts +13 -0
- package/app/root.tsx +60 -0
- package/app/routes/dashboard/detail/history.tsx +73 -0
- package/app/routes/dashboard/detail/layout.tsx +125 -0
- package/app/routes/dashboard/detail/logs.tsx +85 -0
- package/app/routes/dashboard/detail/overview.tsx +119 -0
- package/app/routes/dashboard/detail/requests.tsx +163 -0
- package/app/routes/dashboard/detail/resources.tsx +268 -0
- package/app/routes/dashboard/detail/shared.tsx +59 -0
- package/app/routes/dashboard/index.tsx +360 -0
- package/app/routes/dashboard/layout.tsx +30 -0
- package/app/routes/docs/architecture.tsx +155 -0
- package/app/routes/docs/cli.tsx +122 -0
- package/app/routes/docs/deploying.tsx +105 -0
- package/app/routes/docs/index.tsx +104 -0
- package/app/routes/docs/layout.tsx +58 -0
- package/app/routes/home.tsx +134 -0
- package/app/routes/root.client.tsx +46 -0
- package/app/routes.ts +21 -0
- package/app/styles.css +15 -0
- package/app/theme.css +134 -0
- package/bin/deploy.js +362 -138
- package/docs-site/404.html +33 -0
- package/docs-site/home.tsx +130 -0
- package/docs-site/index.html +35 -0
- package/docs-site/layout.tsx +57 -0
- package/docs-site/main.tsx +41 -0
- package/docs-site/shell.tsx +34 -0
- package/docs-site/styles.css +4 -0
- package/drizzle.config.js +8 -0
- package/examples/docker/Dockerfile +5 -5
- package/examples/docker/server.js +18 -0
- package/examples/node/package.json +3 -11
- package/examples/node/pnpm-lock.yaml +9 -0
- package/examples/node/server.js +12 -0
- package/examples/static/index.html +41 -15
- package/package.json +40 -64
- package/public/favicon.ico +0 -0
- package/react-router-vite/entry.browser.tsx +49 -0
- package/react-router-vite/entry.rsc.single.tsx +7 -0
- package/react-router-vite/entry.rsc.tsx +36 -0
- package/react-router-vite/entry.ssr.tsx +29 -0
- package/react-router-vite/plugin.ts +114 -0
- package/react-router-vite/types.d.ts +11 -0
- package/react-router.config.ts +5 -0
- package/server/api.test.ts +344 -0
- package/server/api.ts +445 -0
- package/server/docker.ts +268 -0
- package/server/index.ts +17 -0
- package/server/metrics-collector.ts +29 -0
- package/server/schema.ts +56 -0
- package/server/store.test.ts +278 -0
- package/server/store.ts +398 -0
- package/tsconfig.json +21 -0
- package/vite.config.ts +45 -0
- package/vite.docs.config.ts +31 -0
- package/.eslintignore +0 -6
- package/.eslintrc +0 -12
- package/.husky/pre-commit +0 -5
- package/.prettierrc +0 -0
- package/.release-it.json +0 -5
- package/CHANGELOG.md +0 -56
- package/__tests__/fixtures/unknown/.gitkeep +0 -0
- package/__tests__/lib/classifier.test.js +0 -49
- package/__tests__/lib/helpers/util.test.js +0 -57
- package/bin/deploy-delete.js +0 -14
- package/bin/deploy-deploy.js +0 -36
- package/bin/deploy-list.js +0 -40
- package/bin/deploy-login.js +0 -43
- package/bin/deploy-logout.js +0 -16
- package/bin/deploy-logs.js +0 -26
- package/bin/deploy-open.js +0 -26
- package/bin/deploy-register.js +0 -45
- package/bin/deploy-server.js +0 -11
- package/bin/deploy-whoami.js +0 -14
- package/examples/docker/index.js +0 -12
- package/examples/node/index.js +0 -8
- package/examples/static/main.css +0 -9
- package/examples/static/out.gifcd +0 -0
- package/generate-docs.js +0 -55
- package/index.js +0 -69
- package/jsdoc.json +0 -27
- package/lib/classifier.js +0 -63
- package/lib/deploy.js +0 -70
- package/lib/helpers/cli.js +0 -262
- package/lib/helpers/util.js +0 -140
- package/lib/models/deployment.js +0 -474
- package/lib/models/request.js +0 -101
- package/lib/models/user.js +0 -147
- package/lib/server.js +0 -211
- package/lib/static/not-found.html +0 -30
- package/lib/static/page-could-not-load.html +0 -30
- package/lib/static/static-server.js +0 -70
- package/website/README.md +0 -41
- package/website/babel.config.js +0 -3
- package/website/docs/api/_category_.yml +0 -1
- package/website/docs/api/lib/classifier.js.md +0 -11
- package/website/docs/api/lib/deploy.js.md +0 -13
- package/website/docs/api/lib/helpers/cli.js.md +0 -193
- package/website/docs/api/lib/helpers/util.js.md +0 -65
- package/website/docs/api/lib/models/deployment.js.md +0 -171
- package/website/docs/api/lib/models/request.js.md +0 -67
- package/website/docs/api/lib/models/user.js.md +0 -92
- package/website/docs/api/lib/server.js.md +0 -0
- package/website/docs/api/lib/static/static-server.js.md +0 -0
- package/website/docs/intro.md +0 -57
- package/website/docusaurus.config.js +0 -82
- package/website/package-lock.json +0 -25218
- package/website/package.json +0 -39
- package/website/sidebars.js +0 -31
- package/website/src/components/HomepageFeatures/index.js +0 -79
- package/website/src/components/HomepageFeatures/styles.module.css +0 -11
- package/website/src/css/custom.css +0 -39
- package/website/src/pages/index.js +0 -57
- package/website/src/pages/index.module.css +0 -23
- package/website/static/.nojekyll +0 -0
- package/website/static/example.gif +0 -0
- package/website/static/example.mov +0 -0
- package/website/static/img/favicon.ico +0 -0
- package/website/static/img/intro/deploy.png +0 -0
- package/website/static/img/intro/logs.png +0 -0
- package/website/static/img/logo.png +0 -0
- package/website/static/img/logo.pxm +0 -0
- package/website/static/img/logo@2x.png +0 -0
package/server/store.ts
ADDED
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
import { mkdirSync, existsSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { randomBytes, createHash } from 'node:crypto';
|
|
4
|
+
import Database from 'better-sqlite3';
|
|
5
|
+
import { drizzle } from 'drizzle-orm/better-sqlite3';
|
|
6
|
+
import { eq } from 'drizzle-orm';
|
|
7
|
+
import { users, deployments, history, requestLogs, resourceMetrics } from './schema.ts';
|
|
8
|
+
import type { RawContainerStats } from './docker.ts';
|
|
9
|
+
|
|
10
|
+
const DATA_DIR = resolve(process.cwd(), '.deploy-data');
|
|
11
|
+
const DB_FILE = resolve(DATA_DIR, 'deploy.db');
|
|
12
|
+
const UPLOADS_DIR = resolve(DATA_DIR, 'uploads');
|
|
13
|
+
|
|
14
|
+
let _sqlite: InstanceType<typeof Database> | null = null;
|
|
15
|
+
let _db: ReturnType<typeof drizzle> | null = null;
|
|
16
|
+
|
|
17
|
+
function getDb() {
|
|
18
|
+
if (_db) return _db;
|
|
19
|
+
|
|
20
|
+
if (!existsSync(DATA_DIR)) mkdirSync(DATA_DIR, { recursive: true });
|
|
21
|
+
if (!existsSync(UPLOADS_DIR)) mkdirSync(UPLOADS_DIR, { recursive: true });
|
|
22
|
+
|
|
23
|
+
_sqlite = new Database(DB_FILE);
|
|
24
|
+
_sqlite.pragma('journal_mode = WAL');
|
|
25
|
+
|
|
26
|
+
_sqlite.exec(`
|
|
27
|
+
CREATE TABLE IF NOT EXISTS users (
|
|
28
|
+
username TEXT PRIMARY KEY,
|
|
29
|
+
password TEXT NOT NULL,
|
|
30
|
+
token TEXT,
|
|
31
|
+
created_at TEXT NOT NULL
|
|
32
|
+
);
|
|
33
|
+
CREATE TABLE IF NOT EXISTS deployments (
|
|
34
|
+
name TEXT PRIMARY KEY,
|
|
35
|
+
type TEXT,
|
|
36
|
+
username TEXT NOT NULL,
|
|
37
|
+
port INTEGER,
|
|
38
|
+
container_id TEXT,
|
|
39
|
+
container_name TEXT,
|
|
40
|
+
directory TEXT,
|
|
41
|
+
created_at TEXT,
|
|
42
|
+
updated_at TEXT
|
|
43
|
+
);
|
|
44
|
+
CREATE TABLE IF NOT EXISTS history (
|
|
45
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
46
|
+
deployment_name TEXT NOT NULL,
|
|
47
|
+
action TEXT NOT NULL,
|
|
48
|
+
username TEXT,
|
|
49
|
+
type TEXT,
|
|
50
|
+
port INTEGER,
|
|
51
|
+
container_id TEXT,
|
|
52
|
+
timestamp TEXT NOT NULL
|
|
53
|
+
);
|
|
54
|
+
CREATE TABLE IF NOT EXISTS request_logs (
|
|
55
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
56
|
+
deployment_name TEXT NOT NULL,
|
|
57
|
+
method TEXT NOT NULL,
|
|
58
|
+
path TEXT NOT NULL,
|
|
59
|
+
status INTEGER NOT NULL,
|
|
60
|
+
duration INTEGER NOT NULL,
|
|
61
|
+
timestamp INTEGER NOT NULL
|
|
62
|
+
);
|
|
63
|
+
CREATE INDEX IF NOT EXISTS idx_request_logs_deployment
|
|
64
|
+
ON request_logs(deployment_name);
|
|
65
|
+
CREATE INDEX IF NOT EXISTS idx_request_logs_timestamp
|
|
66
|
+
ON request_logs(deployment_name, timestamp);
|
|
67
|
+
CREATE INDEX IF NOT EXISTS idx_history_deployment
|
|
68
|
+
ON history(deployment_name);
|
|
69
|
+
CREATE INDEX IF NOT EXISTS idx_deployments_username
|
|
70
|
+
ON deployments(username);
|
|
71
|
+
CREATE TABLE IF NOT EXISTS resource_metrics (
|
|
72
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
73
|
+
deployment_name TEXT NOT NULL,
|
|
74
|
+
cpu_percent REAL NOT NULL,
|
|
75
|
+
mem_usage_bytes INTEGER NOT NULL,
|
|
76
|
+
mem_limit_bytes INTEGER NOT NULL,
|
|
77
|
+
mem_percent REAL NOT NULL,
|
|
78
|
+
net_rx_bytes INTEGER NOT NULL,
|
|
79
|
+
net_tx_bytes INTEGER NOT NULL,
|
|
80
|
+
block_read_bytes INTEGER NOT NULL,
|
|
81
|
+
block_write_bytes INTEGER NOT NULL,
|
|
82
|
+
pids INTEGER NOT NULL,
|
|
83
|
+
timestamp INTEGER NOT NULL
|
|
84
|
+
);
|
|
85
|
+
CREATE INDEX IF NOT EXISTS idx_resource_metrics_deployment
|
|
86
|
+
ON resource_metrics(deployment_name);
|
|
87
|
+
CREATE INDEX IF NOT EXISTS idx_resource_metrics_timestamp
|
|
88
|
+
ON resource_metrics(deployment_name, timestamp);
|
|
89
|
+
`);
|
|
90
|
+
|
|
91
|
+
_db = drizzle(_sqlite);
|
|
92
|
+
return _db;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function _resetDb() {
|
|
96
|
+
if (_sqlite) _sqlite.close();
|
|
97
|
+
_sqlite = null;
|
|
98
|
+
_db = null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function getUploadsDir() {
|
|
102
|
+
return UPLOADS_DIR;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function hashPassword(password: string) {
|
|
106
|
+
return createHash('sha256').update(password).digest('hex');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function generateToken() {
|
|
110
|
+
return randomBytes(32).toString('hex');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ── Users ───────────────────────────────────────────────────────────────────
|
|
114
|
+
|
|
115
|
+
export function registerUser(username: string, password: string) {
|
|
116
|
+
const db = getDb();
|
|
117
|
+
const existing = db.select().from(users).where(eq(users.username, username)).get();
|
|
118
|
+
if (existing) {
|
|
119
|
+
return { error: 'User already exists' as const, status: 409 as const };
|
|
120
|
+
}
|
|
121
|
+
const token = generateToken();
|
|
122
|
+
db.insert(users)
|
|
123
|
+
.values({
|
|
124
|
+
username,
|
|
125
|
+
password: hashPassword(password),
|
|
126
|
+
token,
|
|
127
|
+
createdAt: new Date().toISOString(),
|
|
128
|
+
})
|
|
129
|
+
.run();
|
|
130
|
+
return { token };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function loginUser(username: string, password: string) {
|
|
134
|
+
const db = getDb();
|
|
135
|
+
const user = db.select().from(users).where(eq(users.username, username)).get();
|
|
136
|
+
if (!user || user.password !== hashPassword(password)) {
|
|
137
|
+
return { error: 'Invalid credentials' as const, status: 401 as const };
|
|
138
|
+
}
|
|
139
|
+
const token = generateToken();
|
|
140
|
+
db.update(users).set({ token }).where(eq(users.username, username)).run();
|
|
141
|
+
return { token };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function authenticate(
|
|
145
|
+
username: string | null | undefined,
|
|
146
|
+
token: string | null | undefined,
|
|
147
|
+
) {
|
|
148
|
+
if (!username || !token) return false;
|
|
149
|
+
const db = getDb();
|
|
150
|
+
const user = db.select().from(users).where(eq(users.username, username)).get();
|
|
151
|
+
return user != null && user.token === token;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function logoutUser(username: string) {
|
|
155
|
+
const db = getDb();
|
|
156
|
+
db.update(users).set({ token: null }).where(eq(users.username, username)).run();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function getUser(username: string) {
|
|
160
|
+
const db = getDb();
|
|
161
|
+
const user = db
|
|
162
|
+
.select({ username: users.username, createdAt: users.createdAt })
|
|
163
|
+
.from(users)
|
|
164
|
+
.where(eq(users.username, username))
|
|
165
|
+
.get();
|
|
166
|
+
return user || null;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ── Deployments ─────────────────────────────────────────────────────────────
|
|
170
|
+
|
|
171
|
+
interface DeploymentInput {
|
|
172
|
+
name: string;
|
|
173
|
+
type?: string;
|
|
174
|
+
username: string;
|
|
175
|
+
port?: number;
|
|
176
|
+
containerId?: string;
|
|
177
|
+
containerName?: string;
|
|
178
|
+
directory?: string;
|
|
179
|
+
createdAt?: string;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function saveDeployment(deployment: DeploymentInput) {
|
|
183
|
+
const db = getDb();
|
|
184
|
+
const now = new Date().toISOString();
|
|
185
|
+
db.insert(deployments)
|
|
186
|
+
.values({
|
|
187
|
+
name: deployment.name,
|
|
188
|
+
type: deployment.type || null,
|
|
189
|
+
username: deployment.username,
|
|
190
|
+
port: deployment.port || null,
|
|
191
|
+
containerId: deployment.containerId || null,
|
|
192
|
+
containerName: deployment.containerName || null,
|
|
193
|
+
directory: deployment.directory || null,
|
|
194
|
+
createdAt: deployment.createdAt || now,
|
|
195
|
+
updatedAt: now,
|
|
196
|
+
})
|
|
197
|
+
.onConflictDoUpdate({
|
|
198
|
+
target: deployments.name,
|
|
199
|
+
set: {
|
|
200
|
+
type: deployment.type || null,
|
|
201
|
+
username: deployment.username,
|
|
202
|
+
port: deployment.port || null,
|
|
203
|
+
containerId: deployment.containerId || null,
|
|
204
|
+
containerName: deployment.containerName || null,
|
|
205
|
+
directory: deployment.directory || null,
|
|
206
|
+
updatedAt: now,
|
|
207
|
+
},
|
|
208
|
+
})
|
|
209
|
+
.run();
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function getDeployment(name: string) {
|
|
213
|
+
const db = getDb();
|
|
214
|
+
return db.select().from(deployments).where(eq(deployments.name, name)).get() || null;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export function getDeployments(username: string) {
|
|
218
|
+
const db = getDb();
|
|
219
|
+
return db.select().from(deployments).where(eq(deployments.username, username)).all();
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export function deleteDeployment(name: string) {
|
|
223
|
+
const db = getDb();
|
|
224
|
+
db.delete(deployments).where(eq(deployments.name, name)).run();
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export function getAllDeployments() {
|
|
228
|
+
const db = getDb();
|
|
229
|
+
return db.select().from(deployments).all();
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// ── Deployment history ───────────────────────────────────────────────────────
|
|
233
|
+
|
|
234
|
+
interface DeployEvent {
|
|
235
|
+
action: string;
|
|
236
|
+
username?: string;
|
|
237
|
+
type?: string;
|
|
238
|
+
port?: number;
|
|
239
|
+
containerId?: string;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export function addDeployEvent(name: string, event: DeployEvent) {
|
|
243
|
+
const db = getDb();
|
|
244
|
+
db.insert(history)
|
|
245
|
+
.values({
|
|
246
|
+
deploymentName: name,
|
|
247
|
+
action: event.action,
|
|
248
|
+
username: event.username || null,
|
|
249
|
+
type: event.type || null,
|
|
250
|
+
port: event.port || null,
|
|
251
|
+
containerId: event.containerId || null,
|
|
252
|
+
timestamp: new Date().toISOString(),
|
|
253
|
+
})
|
|
254
|
+
.run();
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export function getDeployHistory(name: string) {
|
|
258
|
+
const db = getDb();
|
|
259
|
+
return db.select().from(history).where(eq(history.deploymentName, name)).all();
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// ── Request logs ────────────────────────────────────────────────────────────
|
|
263
|
+
|
|
264
|
+
const MAX_LOGS_PER_APP = 500;
|
|
265
|
+
|
|
266
|
+
interface RequestEntry {
|
|
267
|
+
method: string;
|
|
268
|
+
path: string;
|
|
269
|
+
status: number;
|
|
270
|
+
duration: number;
|
|
271
|
+
timestamp: number;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export function logRequest(name: string, entry: RequestEntry) {
|
|
275
|
+
const db = getDb();
|
|
276
|
+
db.insert(requestLogs)
|
|
277
|
+
.values({
|
|
278
|
+
deploymentName: name,
|
|
279
|
+
method: entry.method,
|
|
280
|
+
path: entry.path,
|
|
281
|
+
status: entry.status,
|
|
282
|
+
duration: entry.duration,
|
|
283
|
+
timestamp: entry.timestamp,
|
|
284
|
+
})
|
|
285
|
+
.run();
|
|
286
|
+
|
|
287
|
+
// Enforce ring buffer: keep only the most recent MAX_LOGS_PER_APP entries
|
|
288
|
+
_sqlite!
|
|
289
|
+
.prepare(
|
|
290
|
+
`
|
|
291
|
+
DELETE FROM request_logs
|
|
292
|
+
WHERE deployment_name = ? AND id NOT IN (
|
|
293
|
+
SELECT id FROM request_logs
|
|
294
|
+
WHERE deployment_name = ?
|
|
295
|
+
ORDER BY id DESC
|
|
296
|
+
LIMIT ?
|
|
297
|
+
)
|
|
298
|
+
`,
|
|
299
|
+
)
|
|
300
|
+
.run(name, name, MAX_LOGS_PER_APP);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export function getRequestLogs(name: string) {
|
|
304
|
+
const db = getDb();
|
|
305
|
+
return db
|
|
306
|
+
.select({
|
|
307
|
+
method: requestLogs.method,
|
|
308
|
+
path: requestLogs.path,
|
|
309
|
+
status: requestLogs.status,
|
|
310
|
+
duration: requestLogs.duration,
|
|
311
|
+
timestamp: requestLogs.timestamp,
|
|
312
|
+
})
|
|
313
|
+
.from(requestLogs)
|
|
314
|
+
.where(eq(requestLogs.deploymentName, name))
|
|
315
|
+
.all();
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export function getRequestSummary(name: string) {
|
|
319
|
+
const logs = getRequestLogs(name);
|
|
320
|
+
if (logs.length === 0)
|
|
321
|
+
return { total: 0, statusCodes: {} as Record<string, number>, avgDuration: 0, recentRpm: 0 };
|
|
322
|
+
|
|
323
|
+
const statusCodes: Record<string, number> = {};
|
|
324
|
+
let totalDuration = 0;
|
|
325
|
+
for (const log of logs) {
|
|
326
|
+
const group = `${Math.floor(log.status / 100)}xx`;
|
|
327
|
+
statusCodes[group] = (statusCodes[group] || 0) + 1;
|
|
328
|
+
totalDuration += log.duration;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const oneMinAgo = Date.now() - 60_000;
|
|
332
|
+
const recentCount = logs.filter((l) => l.timestamp > oneMinAgo).length;
|
|
333
|
+
|
|
334
|
+
return {
|
|
335
|
+
total: logs.length,
|
|
336
|
+
statusCodes,
|
|
337
|
+
avgDuration: Math.round(totalDuration / logs.length),
|
|
338
|
+
recentRpm: recentCount,
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// ── Resource metrics ───────────────────────────────────────────────────────
|
|
343
|
+
|
|
344
|
+
const MAX_METRICS_PER_APP = 2880; // ~24h at 30s intervals
|
|
345
|
+
|
|
346
|
+
export function logMetrics(name: string, metrics: RawContainerStats) {
|
|
347
|
+
const db = getDb();
|
|
348
|
+
db.insert(resourceMetrics)
|
|
349
|
+
.values({
|
|
350
|
+
deploymentName: name,
|
|
351
|
+
cpuPercent: metrics.cpuPercent,
|
|
352
|
+
memUsageBytes: metrics.memUsageBytes,
|
|
353
|
+
memLimitBytes: metrics.memLimitBytes,
|
|
354
|
+
memPercent: metrics.memPercent,
|
|
355
|
+
netRxBytes: metrics.netRxBytes,
|
|
356
|
+
netTxBytes: metrics.netTxBytes,
|
|
357
|
+
blockReadBytes: metrics.blockReadBytes,
|
|
358
|
+
blockWriteBytes: metrics.blockWriteBytes,
|
|
359
|
+
pids: metrics.pids,
|
|
360
|
+
timestamp: metrics.timestamp,
|
|
361
|
+
})
|
|
362
|
+
.run();
|
|
363
|
+
|
|
364
|
+
_sqlite!
|
|
365
|
+
.prepare(
|
|
366
|
+
`
|
|
367
|
+
DELETE FROM resource_metrics
|
|
368
|
+
WHERE deployment_name = ? AND id NOT IN (
|
|
369
|
+
SELECT id FROM resource_metrics
|
|
370
|
+
WHERE deployment_name = ?
|
|
371
|
+
ORDER BY id DESC
|
|
372
|
+
LIMIT ?
|
|
373
|
+
)
|
|
374
|
+
`,
|
|
375
|
+
)
|
|
376
|
+
.run(name, name, MAX_METRICS_PER_APP);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
export function getMetricsHistory(name: string, since: number) {
|
|
380
|
+
const db = getDb();
|
|
381
|
+
return db
|
|
382
|
+
.select({
|
|
383
|
+
cpuPercent: resourceMetrics.cpuPercent,
|
|
384
|
+
memUsageBytes: resourceMetrics.memUsageBytes,
|
|
385
|
+
memLimitBytes: resourceMetrics.memLimitBytes,
|
|
386
|
+
memPercent: resourceMetrics.memPercent,
|
|
387
|
+
netRxBytes: resourceMetrics.netRxBytes,
|
|
388
|
+
netTxBytes: resourceMetrics.netTxBytes,
|
|
389
|
+
blockReadBytes: resourceMetrics.blockReadBytes,
|
|
390
|
+
blockWriteBytes: resourceMetrics.blockWriteBytes,
|
|
391
|
+
pids: resourceMetrics.pids,
|
|
392
|
+
timestamp: resourceMetrics.timestamp,
|
|
393
|
+
})
|
|
394
|
+
.from(resourceMetrics)
|
|
395
|
+
.where(eq(resourceMetrics.deploymentName, name))
|
|
396
|
+
.all()
|
|
397
|
+
.filter((r) => r.timestamp >= since);
|
|
398
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"allowImportingTsExtensions": true,
|
|
4
|
+
"strict": true,
|
|
5
|
+
"noUnusedLocals": true,
|
|
6
|
+
"noUnusedParameters": true,
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
"verbatimModuleSyntax": true,
|
|
9
|
+
"noEmit": true,
|
|
10
|
+
"moduleResolution": "Bundler",
|
|
11
|
+
"module": "ESNext",
|
|
12
|
+
"target": "ESNext",
|
|
13
|
+
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|
|
14
|
+
"jsx": "react-jsx",
|
|
15
|
+
"baseUrl": ".",
|
|
16
|
+
"paths": {
|
|
17
|
+
"@/*": ["./app/*"]
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"include": ["**/*.ts", "**/*.tsx", "app/types/**/*.d.ts"]
|
|
21
|
+
}
|
package/vite.config.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import tailwindcss from '@tailwindcss/vite';
|
|
2
|
+
import react from '@vitejs/plugin-react';
|
|
3
|
+
import rsc from '@vitejs/plugin-rsc';
|
|
4
|
+
import { defineConfig } from 'vite';
|
|
5
|
+
import { resolve } from 'path';
|
|
6
|
+
import { reactRouter } from './react-router-vite/plugin.js';
|
|
7
|
+
|
|
8
|
+
export default defineConfig({
|
|
9
|
+
clearScreen: false,
|
|
10
|
+
build: {
|
|
11
|
+
minify: false,
|
|
12
|
+
},
|
|
13
|
+
plugins: [
|
|
14
|
+
// import("vite-plugin-inspect").then(m => m.default()),
|
|
15
|
+
tailwindcss(),
|
|
16
|
+
react(),
|
|
17
|
+
reactRouter(),
|
|
18
|
+
rsc({
|
|
19
|
+
entries: {
|
|
20
|
+
client: './react-router-vite/entry.browser.tsx',
|
|
21
|
+
ssr: './react-router-vite/entry.ssr.tsx',
|
|
22
|
+
rsc: './react-router-vite/entry.rsc.single.tsx',
|
|
23
|
+
},
|
|
24
|
+
}),
|
|
25
|
+
],
|
|
26
|
+
resolve: {
|
|
27
|
+
alias: {
|
|
28
|
+
'@': resolve(__dirname, './app'),
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
optimizeDeps: {
|
|
32
|
+
include: ['react-router', 'react-router/internal/react-server-client'],
|
|
33
|
+
exclude: ['better-sqlite3'],
|
|
34
|
+
},
|
|
35
|
+
server: {
|
|
36
|
+
// Allow connections from iOS simulator and local network devices
|
|
37
|
+
host: true,
|
|
38
|
+
// Serve static files from data directory
|
|
39
|
+
fs: {
|
|
40
|
+
allow: ['..', './data'],
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
// Public directory for static assets
|
|
44
|
+
publicDir: 'public',
|
|
45
|
+
}) as any;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import tailwindcss from '@tailwindcss/vite';
|
|
2
|
+
import react from '@vitejs/plugin-react';
|
|
3
|
+
import { defineConfig, type Plugin } from 'vite';
|
|
4
|
+
import { resolve } from 'path';
|
|
5
|
+
import { copyFileSync } from 'fs';
|
|
6
|
+
|
|
7
|
+
function copy404(): Plugin {
|
|
8
|
+
return {
|
|
9
|
+
name: 'copy-404',
|
|
10
|
+
writeBundle(options) {
|
|
11
|
+
copyFileSync(resolve(__dirname, 'docs-site', '404.html'), resolve(options.dir!, '404.html'));
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export default defineConfig({
|
|
17
|
+
root: 'docs-site',
|
|
18
|
+
base: '/deploy.sh/',
|
|
19
|
+
build: {
|
|
20
|
+
outDir: resolve(__dirname, 'dist-docs'),
|
|
21
|
+
emptyOutDir: true,
|
|
22
|
+
minify: false,
|
|
23
|
+
},
|
|
24
|
+
plugins: [tailwindcss(), react(), copy404()],
|
|
25
|
+
resolve: {
|
|
26
|
+
alias: {
|
|
27
|
+
'@': resolve(__dirname, './app'),
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
publicDir: resolve(__dirname, 'public'),
|
|
31
|
+
});
|
package/.eslintignore
DELETED
package/.eslintrc
DELETED
package/.husky/pre-commit
DELETED
package/.prettierrc
DELETED
|
File without changes
|
package/.release-it.json
DELETED
package/CHANGELOG.md
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
# 1.0.0 (01/27/2018)
|
|
2
|
-
|
|
3
|
-
- sub directories would cause deploy to fail, now recursively find the strings and add them manually
|
|
4
|
-
- delete should delete the current working directories deployment if not specified
|
|
5
|
-
- Deployment.del removes the instance metadata from the database using the correct query params
|
|
6
|
-
- refactors CLI to be a class
|
|
7
|
-
- moves from easy-table to turtler
|
|
8
|
-
- fixes login and logout functionality was mixed on cli
|
|
9
|
-
- by default the open command will open the current directory if it is deployed
|
|
10
|
-
- by default the log command will open the current directory if it is deployed
|
|
11
|
-
- logs no longer have a `-` preceding each line
|
|
12
|
-
- logs trim white space instead of adding an empty line
|
|
13
|
-
- delete API actually works now, instead of continuously hanging
|
|
14
|
-
- removes; mkdirp, easy-table, async
|
|
15
|
-
- adds tryitout for docs page generation
|
|
16
|
-
- config is now stored in `${homedir}/.deployrc`
|
|
17
|
-
- getCredentials and cacheCredentials are no longer blocking calls, they will happen async
|
|
18
|
-
- all error responses from the server will contain an error object
|
|
19
|
-
- not-found (application not deployed) and page-could-not-load (proxy errors) pages are now moved into a static directory
|
|
20
|
-
- main landing page is rendered with tryitout
|
|
21
|
-
|
|
22
|
-
# 0.2.1 (08/15/2017)
|
|
23
|
-
|
|
24
|
-
- adds the ability to delete deployment and its assets
|
|
25
|
-
- be able to get container status by querying the container on the get call (add to decorator function) is now visible when running (deploy ls) as a status column
|
|
26
|
-
|
|
27
|
-
# 0.2.0 (08/14/2017)
|
|
28
|
-
|
|
29
|
-
- deployment model now contains amount of requests
|
|
30
|
-
- stops overloading Deployment.get and breaks out functionality into Deployment.get and Deployment.getAll
|
|
31
|
-
- the request model now captures the http verb associated with the request
|
|
32
|
-
- now captures statusCode for responses in the request model
|
|
33
|
-
- startup and shutdown is now coordinated and less prone to breaking
|
|
34
|
-
|
|
35
|
-
# 0.1.1 (08/13/2017)
|
|
36
|
-
|
|
37
|
-
- now is packaged as a universal binary
|
|
38
|
-
- fixes failure when no logs are retrieved from server
|
|
39
|
-
|
|
40
|
-
# 0.1.0 (08/13/2017)
|
|
41
|
-
|
|
42
|
-
- adds api and cli action to be able retrieve logs
|
|
43
|
-
- deals with cleaning up old images
|
|
44
|
-
- deletes image and container when application is being redeployed
|
|
45
|
-
- further consolidates deployment logic into the deployment model
|
|
46
|
-
- starts up containers from cold start
|
|
47
|
-
- shuts down containers when process is closing
|
|
48
|
-
- adds caching to static-server
|
|
49
|
-
- abstract models into their own files and their own collections
|
|
50
|
-
- fixes the middleware request logger
|
|
51
|
-
- fixes CLI responses
|
|
52
|
-
- adds whoami functionality that will show the current logged in user (`deploy whoami`)
|
|
53
|
-
|
|
54
|
-
# 0.0.1 (08/08/2017)
|
|
55
|
-
|
|
56
|
-
- basic functionality working
|
|
File without changes
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
const path = require("path");
|
|
2
|
-
|
|
3
|
-
const classifier = require("../../lib/classifier");
|
|
4
|
-
|
|
5
|
-
describe("@lib/classifier", () => {
|
|
6
|
-
const baseDirectory = path.resolve(__dirname, "..", "..", "examples");
|
|
7
|
-
|
|
8
|
-
test("should be able to classify static site", async () => {
|
|
9
|
-
const directory = path.resolve(baseDirectory, "static");
|
|
10
|
-
const output = await classifier(directory);
|
|
11
|
-
|
|
12
|
-
expect(output).toEqual({
|
|
13
|
-
type: "static",
|
|
14
|
-
build: `\n FROM mhart/alpine-node:base-8\n WORKDIR ${directory}\n ADD . .\n\n CMD ["node", "index.js"]\n `,
|
|
15
|
-
});
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
test("should be able to classify node site", async () => {
|
|
19
|
-
const directory = path.resolve(baseDirectory, "node");
|
|
20
|
-
const output = await classifier(directory);
|
|
21
|
-
|
|
22
|
-
expect(output).toEqual({
|
|
23
|
-
type: "node",
|
|
24
|
-
build: `\n FROM mhart/alpine-node:8\n WORKDIR ${directory}\n ADD . .\n\n RUN npm install\n\n CMD ["npm", "start"]\n `,
|
|
25
|
-
});
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
test("should be able to classify docker site", async () => {
|
|
29
|
-
const directory = path.resolve(baseDirectory, "docker");
|
|
30
|
-
const output = await classifier(directory);
|
|
31
|
-
|
|
32
|
-
expect(output).toEqual({
|
|
33
|
-
type: "docker",
|
|
34
|
-
build: `FROM mhart/alpine-node:8\nWORKDIR ${directory}\nADD . .\n\nCMD ["node", "index.js"]\n`,
|
|
35
|
-
});
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
test("should be able to classify unknown deploy target", async () => {
|
|
39
|
-
const directory = path.resolve(
|
|
40
|
-
path.resolve(__dirname, "..", "fixtures"),
|
|
41
|
-
"unknown"
|
|
42
|
-
);
|
|
43
|
-
const output = await classifier(directory);
|
|
44
|
-
|
|
45
|
-
expect(output).toEqual({
|
|
46
|
-
type: "unknown",
|
|
47
|
-
});
|
|
48
|
-
});
|
|
49
|
-
});
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
const os = require("os");
|
|
2
|
-
const fs = require("fs");
|
|
3
|
-
|
|
4
|
-
const {
|
|
5
|
-
getPort,
|
|
6
|
-
hash,
|
|
7
|
-
contains,
|
|
8
|
-
mk,
|
|
9
|
-
rm,
|
|
10
|
-
} = require("../../../lib/helpers/util");
|
|
11
|
-
|
|
12
|
-
describe("@lib/util", () => {
|
|
13
|
-
test("@getPort: should be able return a valid port", async () => {
|
|
14
|
-
let port = await getPort();
|
|
15
|
-
let port1 = await getPort();
|
|
16
|
-
|
|
17
|
-
expect(Number.isInteger(port)).toBeTruthy();
|
|
18
|
-
expect(Number.isInteger(port1)).toBeTruthy();
|
|
19
|
-
expect(port !== port1).toBeTruthy();
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
test("@hash: should return a proper lowercase hash", () => {
|
|
23
|
-
const ret = hash(6);
|
|
24
|
-
expect(ret.length).toBeTruthy();
|
|
25
|
-
expect(ret.toLowerCase()).toBeTruthy();
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
test("@contains: should be able to return a proper conditional for truthy and false cases", () => {
|
|
29
|
-
expect(
|
|
30
|
-
contains(
|
|
31
|
-
["index.html", "main.css"],
|
|
32
|
-
["index.html", "!Dockerfile", "!package.json"]
|
|
33
|
-
)
|
|
34
|
-
).toBeTruthy();
|
|
35
|
-
expect(
|
|
36
|
-
contains(
|
|
37
|
-
["index.html", "main.css", "Dockerfile"],
|
|
38
|
-
["index.html", "!Dockerfile", "!package.json"]
|
|
39
|
-
)
|
|
40
|
-
).toBeFalsy();
|
|
41
|
-
expect(
|
|
42
|
-
contains([], ["index.html", "!Dockerfile", "!package.json"])
|
|
43
|
-
).toBeFalsy();
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
test("@mk / @rm: should be able to make recursive directory", async () => {
|
|
47
|
-
let directory = `${os.tmpdir()}/hello/world/this/is/a/nested/directory`;
|
|
48
|
-
|
|
49
|
-
await mk(directory);
|
|
50
|
-
|
|
51
|
-
expect(fs.existsSync(directory)).toBeTruthy();
|
|
52
|
-
|
|
53
|
-
await rm(directory);
|
|
54
|
-
|
|
55
|
-
expect(!fs.existsSync(directory)).toBeTruthy();
|
|
56
|
-
});
|
|
57
|
-
});
|