everything-dev 0.0.15 → 0.0.17
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/package.json +1 -1
- package/src/cli.ts +236 -25
- package/src/contract.ts +117 -19
- package/src/lib/nova.ts +2 -2
- package/src/lib/orchestrator.ts +21 -20
- package/src/lib/process-registry.ts +157 -0
- package/src/lib/process.ts +8 -10
- package/src/lib/secrets.ts +1 -0
- package/src/lib/sync.ts +134 -0
- package/src/plugin.ts +360 -113
- package/src/types.ts +15 -2
package/src/plugin.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { Effect } from "every-plugin/effect";
|
|
|
3
3
|
import { z } from "every-plugin/zod";
|
|
4
4
|
import { Graph } from "near-social-js";
|
|
5
5
|
|
|
6
|
+
import { createProcessRegistry } from "./lib/process-registry";
|
|
6
7
|
import {
|
|
7
8
|
type AppConfig,
|
|
8
9
|
type BosConfig as BosConfigType,
|
|
@@ -34,6 +35,7 @@ import {
|
|
|
34
35
|
verifyNovaCredentials
|
|
35
36
|
} from "./lib/nova";
|
|
36
37
|
import { type AppOrchestrator, startApp } from "./lib/orchestrator";
|
|
38
|
+
import { syncFiles } from "./lib/sync";
|
|
37
39
|
import { run } from "./utils/run";
|
|
38
40
|
import { colors, icons } from "./utils/theme";
|
|
39
41
|
|
|
@@ -54,6 +56,27 @@ function getGatewayDomain(config: BosConfigType): string {
|
|
|
54
56
|
throw new Error("bos.config.json must have a 'gateway' field with production URL");
|
|
55
57
|
}
|
|
56
58
|
|
|
59
|
+
function getAccountForNetwork(config: BosConfigType, network: "mainnet" | "testnet"): string {
|
|
60
|
+
if (network === "testnet") {
|
|
61
|
+
if (!config.testnet) {
|
|
62
|
+
throw new Error("bos.config.json must have a 'testnet' field to use testnet network");
|
|
63
|
+
}
|
|
64
|
+
return config.testnet;
|
|
65
|
+
}
|
|
66
|
+
return config.account;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function getSocialContract(network: "mainnet" | "testnet"): string {
|
|
70
|
+
return network === "testnet" ? "v1.social08.testnet" : "social.near";
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function getSocialExplorerUrl(network: "mainnet" | "testnet", path: string): string {
|
|
74
|
+
const baseUrl = network === "testnet"
|
|
75
|
+
? "https://test.near.social"
|
|
76
|
+
: "https://near.social";
|
|
77
|
+
return `${baseUrl}/${path}`;
|
|
78
|
+
}
|
|
79
|
+
|
|
57
80
|
function buildSocialSetArgs(account: string, gatewayDomain: string, config: BosConfigType): object {
|
|
58
81
|
return {
|
|
59
82
|
data: {
|
|
@@ -201,6 +224,7 @@ export default createPlugin({
|
|
|
201
224
|
env,
|
|
202
225
|
description,
|
|
203
226
|
appConfig,
|
|
227
|
+
bosConfig: deps.bosConfig ?? undefined,
|
|
204
228
|
port: input.port,
|
|
205
229
|
interactive: input.interactive,
|
|
206
230
|
};
|
|
@@ -236,7 +260,9 @@ export default createPlugin({
|
|
|
236
260
|
}
|
|
237
261
|
if (typeof current === "string") {
|
|
238
262
|
remoteConfig = JSON.parse(current) as BosConfigType;
|
|
239
|
-
|
|
263
|
+
const configFilePath = `${process.cwd()}/bos.config.json`;
|
|
264
|
+
await Bun.write(configFilePath, JSON.stringify(remoteConfig, null, 2));
|
|
265
|
+
setConfig(remoteConfig, process.cwd());
|
|
240
266
|
}
|
|
241
267
|
}
|
|
242
268
|
} catch (error) {
|
|
@@ -297,6 +323,7 @@ export default createPlugin({
|
|
|
297
323
|
ui: "remote",
|
|
298
324
|
api: "remote",
|
|
299
325
|
},
|
|
326
|
+
bosConfig: config,
|
|
300
327
|
port,
|
|
301
328
|
interactive: input.interactive,
|
|
302
329
|
noLogs: true,
|
|
@@ -312,6 +339,7 @@ export default createPlugin({
|
|
|
312
339
|
|
|
313
340
|
serve: builder.serve.handler(async ({ input }) => {
|
|
314
341
|
const port = input.port;
|
|
342
|
+
|
|
315
343
|
return {
|
|
316
344
|
status: "serving" as const,
|
|
317
345
|
url: `http://localhost:${port}`,
|
|
@@ -381,7 +409,7 @@ export default createPlugin({
|
|
|
381
409
|
}),
|
|
382
410
|
|
|
383
411
|
publish: builder.publish.handler(async ({ input: publishInput }) => {
|
|
384
|
-
const {
|
|
412
|
+
const { bosConfig, nearPrivateKey } = deps;
|
|
385
413
|
|
|
386
414
|
if (!bosConfig) {
|
|
387
415
|
return {
|
|
@@ -392,45 +420,49 @@ export default createPlugin({
|
|
|
392
420
|
};
|
|
393
421
|
}
|
|
394
422
|
|
|
395
|
-
const
|
|
396
|
-
const socialPath = `${bosConfig.account}/bos/gateways/${gatewayDomain}/bos.config.json`;
|
|
423
|
+
const network = publishInput.network;
|
|
397
424
|
|
|
398
|
-
|
|
399
|
-
|
|
425
|
+
try {
|
|
426
|
+
const account = getAccountForNetwork(bosConfig, network);
|
|
427
|
+
const gatewayDomain = getGatewayDomain(bosConfig);
|
|
428
|
+
const socialContract = getSocialContract(network);
|
|
429
|
+
const socialPath = `${account}/bos/gateways/${gatewayDomain}/bos.config.json`;
|
|
400
430
|
|
|
401
|
-
const
|
|
402
|
-
|
|
431
|
+
const publishEffect = Effect.gen(function* () {
|
|
432
|
+
yield* ensureNearCli;
|
|
433
|
+
|
|
434
|
+
const bosEnv = yield* loadBosEnv;
|
|
435
|
+
const privateKey = nearPrivateKey || bosEnv.NEAR_PRIVATE_KEY;
|
|
436
|
+
|
|
437
|
+
const socialArgs = buildSocialSetArgs(account, gatewayDomain, bosConfig);
|
|
438
|
+
const argsBase64 = Buffer.from(JSON.stringify(socialArgs)).toString("base64");
|
|
439
|
+
|
|
440
|
+
if (publishInput.dryRun) {
|
|
441
|
+
return {
|
|
442
|
+
status: "dry-run" as const,
|
|
443
|
+
txHash: "",
|
|
444
|
+
registryUrl: getSocialExplorerUrl(network, socialPath),
|
|
445
|
+
};
|
|
446
|
+
}
|
|
403
447
|
|
|
404
|
-
|
|
405
|
-
|
|
448
|
+
const result = yield* executeTransaction({
|
|
449
|
+
account,
|
|
450
|
+
contract: socialContract,
|
|
451
|
+
method: "set",
|
|
452
|
+
argsBase64,
|
|
453
|
+
network,
|
|
454
|
+
privateKey,
|
|
455
|
+
gas: "300Tgas",
|
|
456
|
+
deposit: "0.05NEAR",
|
|
457
|
+
});
|
|
406
458
|
|
|
407
|
-
if (publishInput.dryRun) {
|
|
408
459
|
return {
|
|
409
|
-
status: "
|
|
410
|
-
txHash: "",
|
|
411
|
-
registryUrl:
|
|
460
|
+
status: "published" as const,
|
|
461
|
+
txHash: result.txHash || "unknown",
|
|
462
|
+
registryUrl: getSocialExplorerUrl(network, socialPath),
|
|
412
463
|
};
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
const result = yield* executeTransaction({
|
|
416
|
-
account: bosConfig.account,
|
|
417
|
-
contract: "social.near",
|
|
418
|
-
method: "set",
|
|
419
|
-
argsBase64,
|
|
420
|
-
network: publishInput.network,
|
|
421
|
-
privateKey,
|
|
422
|
-
gas: "300Tgas",
|
|
423
|
-
deposit: "0.05NEAR",
|
|
424
464
|
});
|
|
425
465
|
|
|
426
|
-
return {
|
|
427
|
-
status: "published" as const,
|
|
428
|
-
txHash: result.txHash || "unknown",
|
|
429
|
-
registryUrl: `https://near.social/${socialPath}`,
|
|
430
|
-
};
|
|
431
|
-
});
|
|
432
|
-
|
|
433
|
-
try {
|
|
434
466
|
return await Effect.runPromise(publishEffect);
|
|
435
467
|
} catch (error) {
|
|
436
468
|
return {
|
|
@@ -456,7 +488,16 @@ export default createPlugin({
|
|
|
456
488
|
gateway: "near-everything/every-plugin/demo/gateway",
|
|
457
489
|
};
|
|
458
490
|
|
|
459
|
-
const
|
|
491
|
+
const getTemplate = (): string => {
|
|
492
|
+
if (input.template) return input.template;
|
|
493
|
+
if (input.type === "project") {
|
|
494
|
+
return deps.bosConfig?.template || DEFAULT_TEMPLATES.project;
|
|
495
|
+
}
|
|
496
|
+
const appConfig = deps.bosConfig?.app[input.type] as { template?: string } | undefined;
|
|
497
|
+
return appConfig?.template || DEFAULT_TEMPLATES[input.type];
|
|
498
|
+
};
|
|
499
|
+
|
|
500
|
+
const template = getTemplate();
|
|
460
501
|
const dest = input.type === "project" ? input.name! : input.type;
|
|
461
502
|
|
|
462
503
|
try {
|
|
@@ -465,11 +506,11 @@ export default createPlugin({
|
|
|
465
506
|
if (input.type === "project" && input.name) {
|
|
466
507
|
const newConfig = {
|
|
467
508
|
account: `${input.name}.near`,
|
|
509
|
+
template: DEFAULT_TEMPLATES.project,
|
|
468
510
|
gateway: {
|
|
469
511
|
development: "http://localhost:8787",
|
|
470
512
|
production: `https://gateway.${input.name}.example.com`,
|
|
471
513
|
},
|
|
472
|
-
create: DEFAULT_TEMPLATES,
|
|
473
514
|
app: {
|
|
474
515
|
host: {
|
|
475
516
|
title: input.name,
|
|
@@ -630,41 +671,44 @@ export default createPlugin({
|
|
|
630
671
|
};
|
|
631
672
|
}
|
|
632
673
|
|
|
633
|
-
const
|
|
634
|
-
yield* ensureNearCli;
|
|
674
|
+
const network = input.network;
|
|
635
675
|
|
|
636
|
-
|
|
637
|
-
const
|
|
676
|
+
try {
|
|
677
|
+
const parentAccount = getAccountForNetwork(bosConfig, network);
|
|
678
|
+
const fullAccount = `${input.name}.${parentAccount}`;
|
|
638
679
|
|
|
639
|
-
const
|
|
640
|
-
|
|
680
|
+
const registerEffect = Effect.gen(function* () {
|
|
681
|
+
yield* ensureNearCli;
|
|
641
682
|
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
parentAccount,
|
|
645
|
-
initialBalance: "0.1NEAR",
|
|
646
|
-
network: input.network,
|
|
647
|
-
privateKey: gatewayPrivateKey,
|
|
648
|
-
});
|
|
683
|
+
const bosEnv = yield* loadBosEnv;
|
|
684
|
+
const gatewayPrivateKey = bosEnv.GATEWAY_PRIVATE_KEY;
|
|
649
685
|
|
|
650
|
-
|
|
651
|
-
|
|
686
|
+
yield* createSubaccount({
|
|
687
|
+
newAccount: fullAccount,
|
|
688
|
+
parentAccount,
|
|
689
|
+
initialBalance: "0.1NEAR",
|
|
690
|
+
network,
|
|
691
|
+
privateKey: gatewayPrivateKey,
|
|
692
|
+
});
|
|
652
693
|
|
|
653
|
-
|
|
694
|
+
const novaConfig = yield* getNovaConfig;
|
|
695
|
+
const nova = createNovaClient(novaConfig);
|
|
654
696
|
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
697
|
+
yield* registerSecretsGroup(nova, fullAccount, parentAccount);
|
|
698
|
+
|
|
699
|
+
return {
|
|
700
|
+
status: "registered" as const,
|
|
701
|
+
account: fullAccount,
|
|
702
|
+
novaGroup: getSecretsGroupId(fullAccount),
|
|
703
|
+
};
|
|
704
|
+
});
|
|
661
705
|
|
|
662
|
-
try {
|
|
663
706
|
return await Effect.runPromise(registerEffect);
|
|
664
707
|
} catch (error) {
|
|
708
|
+
const parentAccount = network === "testnet" ? bosConfig.testnet : bosConfig.account;
|
|
665
709
|
return {
|
|
666
710
|
status: "error" as const,
|
|
667
|
-
account: `${input.name}.${bosConfig.account}`,
|
|
711
|
+
account: `${input.name}.${parentAccount || bosConfig.account}`,
|
|
668
712
|
error: error instanceof Error ? error.message : "Unknown error",
|
|
669
713
|
};
|
|
670
714
|
}
|
|
@@ -1145,7 +1189,7 @@ export default createPlugin({
|
|
|
1145
1189
|
}
|
|
1146
1190
|
}),
|
|
1147
1191
|
|
|
1148
|
-
|
|
1192
|
+
filesSync: builder.filesSync.handler(async ({ input }) => {
|
|
1149
1193
|
const { configDir, bosConfig } = deps;
|
|
1150
1194
|
|
|
1151
1195
|
if (!bosConfig) {
|
|
@@ -1156,62 +1200,26 @@ export default createPlugin({
|
|
|
1156
1200
|
};
|
|
1157
1201
|
}
|
|
1158
1202
|
|
|
1159
|
-
const
|
|
1160
|
-
const
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
status: "error" as const,
|
|
1165
|
-
synced: [],
|
|
1166
|
-
error: `No shared.${category} dependencies found in bos.config.json`,
|
|
1167
|
-
};
|
|
1168
|
-
}
|
|
1169
|
-
|
|
1170
|
-
try {
|
|
1171
|
-
const rootPkgPath = `${configDir}/package.json`;
|
|
1172
|
-
const rootPkg = await Bun.file(rootPkgPath).json() as {
|
|
1173
|
-
workspaces?: { packages?: string[]; catalog?: Record<string, string> };
|
|
1174
|
-
};
|
|
1175
|
-
|
|
1176
|
-
if (!rootPkg.workspaces) {
|
|
1177
|
-
rootPkg.workspaces = {};
|
|
1178
|
-
}
|
|
1179
|
-
if (!rootPkg.workspaces.catalog) {
|
|
1180
|
-
rootPkg.workspaces.catalog = {};
|
|
1181
|
-
}
|
|
1182
|
-
|
|
1183
|
-
const synced: string[] = [];
|
|
1184
|
-
|
|
1185
|
-
for (const [name, config] of Object.entries(sharedDeps)) {
|
|
1186
|
-
const version = (config as { requiredVersion?: string }).requiredVersion;
|
|
1187
|
-
if (version) {
|
|
1188
|
-
const cleanVersion = version.replace(/^[\^~]/, "");
|
|
1189
|
-
rootPkg.workspaces.catalog[name] = cleanVersion;
|
|
1190
|
-
synced.push(name);
|
|
1191
|
-
}
|
|
1192
|
-
}
|
|
1203
|
+
const rootPkgPath = `${configDir}/package.json`;
|
|
1204
|
+
const rootPkg = await Bun.file(rootPkgPath).json() as {
|
|
1205
|
+
workspaces?: { catalog?: Record<string, string> };
|
|
1206
|
+
};
|
|
1207
|
+
const catalog = rootPkg.workspaces?.catalog ?? {};
|
|
1193
1208
|
|
|
1194
|
-
|
|
1209
|
+
const packages = input.packages || Object.keys(bosConfig.app);
|
|
1195
1210
|
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1211
|
+
const synced = await syncFiles({
|
|
1212
|
+
configDir,
|
|
1213
|
+
packages,
|
|
1214
|
+
bosConfig,
|
|
1215
|
+
catalog,
|
|
1216
|
+
force: input.force,
|
|
1217
|
+
});
|
|
1203
1218
|
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
} catch (error) {
|
|
1209
|
-
return {
|
|
1210
|
-
status: "error" as const,
|
|
1211
|
-
synced: [],
|
|
1212
|
-
error: error instanceof Error ? error.message : "Unknown error",
|
|
1213
|
-
};
|
|
1214
|
-
}
|
|
1219
|
+
return {
|
|
1220
|
+
status: "synced" as const,
|
|
1221
|
+
synced,
|
|
1222
|
+
};
|
|
1215
1223
|
}),
|
|
1216
1224
|
|
|
1217
1225
|
sync: builder.sync.handler(async ({ input }) => {
|
|
@@ -1348,6 +1356,22 @@ export default createPlugin({
|
|
|
1348
1356
|
}
|
|
1349
1357
|
}
|
|
1350
1358
|
|
|
1359
|
+
let filesSynced: Array<{ package: string; files: string[] }> | undefined;
|
|
1360
|
+
|
|
1361
|
+
if (input.files) {
|
|
1362
|
+
const results = await syncFiles({
|
|
1363
|
+
configDir,
|
|
1364
|
+
packages: Object.keys(bosConfig.app),
|
|
1365
|
+
bosConfig,
|
|
1366
|
+
catalog: rootPkg.workspaces?.catalog ?? {},
|
|
1367
|
+
force: input.force,
|
|
1368
|
+
});
|
|
1369
|
+
|
|
1370
|
+
if (results.length > 0) {
|
|
1371
|
+
filesSynced = results.map(r => ({ package: r.package, files: r.files }));
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1351
1375
|
return {
|
|
1352
1376
|
status: "synced" as const,
|
|
1353
1377
|
account,
|
|
@@ -1355,6 +1379,7 @@ export default createPlugin({
|
|
|
1355
1379
|
hostUrl,
|
|
1356
1380
|
catalogUpdated: true,
|
|
1357
1381
|
packagesUpdated,
|
|
1382
|
+
filesSynced,
|
|
1358
1383
|
};
|
|
1359
1384
|
} catch (error) {
|
|
1360
1385
|
return {
|
|
@@ -1368,5 +1393,227 @@ export default createPlugin({
|
|
|
1368
1393
|
};
|
|
1369
1394
|
}
|
|
1370
1395
|
}),
|
|
1396
|
+
|
|
1397
|
+
kill: builder.kill.handler(async ({ input }) => {
|
|
1398
|
+
const killEffect = Effect.gen(function* () {
|
|
1399
|
+
const registry = yield* createProcessRegistry();
|
|
1400
|
+
const result = yield* registry.killAll(input.force);
|
|
1401
|
+
return {
|
|
1402
|
+
status: "killed" as const,
|
|
1403
|
+
killed: result.killed,
|
|
1404
|
+
failed: result.failed,
|
|
1405
|
+
};
|
|
1406
|
+
});
|
|
1407
|
+
|
|
1408
|
+
try {
|
|
1409
|
+
return await Effect.runPromise(killEffect);
|
|
1410
|
+
} catch (error) {
|
|
1411
|
+
return {
|
|
1412
|
+
status: "error" as const,
|
|
1413
|
+
killed: [],
|
|
1414
|
+
failed: [],
|
|
1415
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
1416
|
+
};
|
|
1417
|
+
}
|
|
1418
|
+
}),
|
|
1419
|
+
|
|
1420
|
+
ps: builder.ps.handler(async () => {
|
|
1421
|
+
const psEffect = Effect.gen(function* () {
|
|
1422
|
+
const registry = yield* createProcessRegistry();
|
|
1423
|
+
const processes = yield* registry.getAll();
|
|
1424
|
+
return {
|
|
1425
|
+
status: "listed" as const,
|
|
1426
|
+
processes,
|
|
1427
|
+
};
|
|
1428
|
+
});
|
|
1429
|
+
|
|
1430
|
+
try {
|
|
1431
|
+
return await Effect.runPromise(psEffect);
|
|
1432
|
+
} catch (error) {
|
|
1433
|
+
return {
|
|
1434
|
+
status: "error" as const,
|
|
1435
|
+
processes: [],
|
|
1436
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
1437
|
+
};
|
|
1438
|
+
}
|
|
1439
|
+
}),
|
|
1440
|
+
|
|
1441
|
+
dockerBuild: builder.dockerBuild.handler(async ({ input }) => {
|
|
1442
|
+
const { configDir, bosConfig } = deps;
|
|
1443
|
+
|
|
1444
|
+
const dockerEffect = Effect.gen(function* () {
|
|
1445
|
+
const { execa } = yield* Effect.tryPromise({
|
|
1446
|
+
try: () => import("execa"),
|
|
1447
|
+
catch: (e) => new Error(`Failed to import execa: ${e}`),
|
|
1448
|
+
});
|
|
1449
|
+
|
|
1450
|
+
const dockerfile = input.target === "development" ? "Dockerfile.dev" : "Dockerfile";
|
|
1451
|
+
const imageName = bosConfig?.account?.replace(/\./g, "-") || "bos-app";
|
|
1452
|
+
const tag = input.tag || (input.target === "development" ? "dev" : "latest");
|
|
1453
|
+
const fullTag = `${imageName}:${tag}`;
|
|
1454
|
+
|
|
1455
|
+
const args = ["build", "-f", dockerfile, "-t", fullTag];
|
|
1456
|
+
if (input.noCache) {
|
|
1457
|
+
args.push("--no-cache");
|
|
1458
|
+
}
|
|
1459
|
+
args.push(".");
|
|
1460
|
+
|
|
1461
|
+
yield* Effect.tryPromise({
|
|
1462
|
+
try: () => execa("docker", args, {
|
|
1463
|
+
cwd: configDir,
|
|
1464
|
+
stdio: "inherit",
|
|
1465
|
+
}),
|
|
1466
|
+
catch: (e) => new Error(`Docker build failed: ${e}`),
|
|
1467
|
+
});
|
|
1468
|
+
|
|
1469
|
+
return {
|
|
1470
|
+
status: "built" as const,
|
|
1471
|
+
image: imageName,
|
|
1472
|
+
tag: fullTag,
|
|
1473
|
+
};
|
|
1474
|
+
});
|
|
1475
|
+
|
|
1476
|
+
try {
|
|
1477
|
+
return await Effect.runPromise(dockerEffect);
|
|
1478
|
+
} catch (error) {
|
|
1479
|
+
return {
|
|
1480
|
+
status: "error" as const,
|
|
1481
|
+
image: "",
|
|
1482
|
+
tag: "",
|
|
1483
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
1484
|
+
};
|
|
1485
|
+
}
|
|
1486
|
+
}),
|
|
1487
|
+
|
|
1488
|
+
dockerRun: builder.dockerRun.handler(async ({ input }) => {
|
|
1489
|
+
const { bosConfig } = deps;
|
|
1490
|
+
|
|
1491
|
+
const dockerEffect = Effect.gen(function* () {
|
|
1492
|
+
const { execa } = yield* Effect.tryPromise({
|
|
1493
|
+
try: () => import("execa"),
|
|
1494
|
+
catch: (e) => new Error(`Failed to import execa: ${e}`),
|
|
1495
|
+
});
|
|
1496
|
+
|
|
1497
|
+
const imageName = bosConfig?.account?.replace(/\./g, "-") || "bos-app";
|
|
1498
|
+
const tag = input.target === "development" ? "dev" : "latest";
|
|
1499
|
+
const fullTag = `${imageName}:${tag}`;
|
|
1500
|
+
const port = input.port || (input.target === "development" ? 4000 : 3000);
|
|
1501
|
+
|
|
1502
|
+
const args = ["run"];
|
|
1503
|
+
|
|
1504
|
+
if (input.detach) {
|
|
1505
|
+
args.push("-d");
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
args.push("-p", `${port}:${port}`);
|
|
1509
|
+
args.push("-e", `PORT=${port}`);
|
|
1510
|
+
|
|
1511
|
+
if (input.target === "development") {
|
|
1512
|
+
args.push("-e", `MODE=${input.mode}`);
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
if (input.env) {
|
|
1516
|
+
for (const [key, value] of Object.entries(input.env)) {
|
|
1517
|
+
args.push("-e", `${key}=${value}`);
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
if (bosConfig) {
|
|
1522
|
+
args.push("-e", `BOS_ACCOUNT=${bosConfig.account}`);
|
|
1523
|
+
const gateway = bosConfig.gateway as { production?: string } | string | undefined;
|
|
1524
|
+
if (gateway) {
|
|
1525
|
+
const domain = typeof gateway === "string"
|
|
1526
|
+
? gateway
|
|
1527
|
+
: gateway.production?.replace(/^https?:\/\//, "") || "";
|
|
1528
|
+
if (domain) {
|
|
1529
|
+
args.push("-e", `GATEWAY_DOMAIN=${domain}`);
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
args.push(fullTag);
|
|
1535
|
+
|
|
1536
|
+
const result = yield* Effect.tryPromise({
|
|
1537
|
+
try: () => execa("docker", args, {
|
|
1538
|
+
stdio: input.detach ? "pipe" : "inherit",
|
|
1539
|
+
}),
|
|
1540
|
+
catch: (e) => new Error(`Docker run failed: ${e}`),
|
|
1541
|
+
});
|
|
1542
|
+
|
|
1543
|
+
const containerId = input.detach && result.stdout ? result.stdout.trim().slice(0, 12) : "attached";
|
|
1544
|
+
|
|
1545
|
+
return {
|
|
1546
|
+
status: "running" as const,
|
|
1547
|
+
containerId,
|
|
1548
|
+
url: `http://localhost:${port}`,
|
|
1549
|
+
};
|
|
1550
|
+
});
|
|
1551
|
+
|
|
1552
|
+
try {
|
|
1553
|
+
return await Effect.runPromise(dockerEffect);
|
|
1554
|
+
} catch (error) {
|
|
1555
|
+
return {
|
|
1556
|
+
status: "error" as const,
|
|
1557
|
+
containerId: "",
|
|
1558
|
+
url: "",
|
|
1559
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
1560
|
+
};
|
|
1561
|
+
}
|
|
1562
|
+
}),
|
|
1563
|
+
|
|
1564
|
+
dockerStop: builder.dockerStop.handler(async ({ input }) => {
|
|
1565
|
+
const { bosConfig } = deps;
|
|
1566
|
+
|
|
1567
|
+
const dockerEffect = Effect.gen(function* () {
|
|
1568
|
+
const { execa } = yield* Effect.tryPromise({
|
|
1569
|
+
try: () => import("execa"),
|
|
1570
|
+
catch: (e) => new Error(`Failed to import execa: ${e}`),
|
|
1571
|
+
});
|
|
1572
|
+
|
|
1573
|
+
const stopped: string[] = [];
|
|
1574
|
+
|
|
1575
|
+
if (input.containerId) {
|
|
1576
|
+
yield* Effect.tryPromise({
|
|
1577
|
+
try: () => execa("docker", ["stop", input.containerId!]),
|
|
1578
|
+
catch: (e) => new Error(`Failed to stop container: ${e}`),
|
|
1579
|
+
});
|
|
1580
|
+
stopped.push(input.containerId!);
|
|
1581
|
+
} else if (input.all) {
|
|
1582
|
+
const imageName = bosConfig?.account?.replace(/\./g, "-") || "bos-app";
|
|
1583
|
+
|
|
1584
|
+
const psResult = yield* Effect.tryPromise({
|
|
1585
|
+
try: () => execa("docker", ["ps", "-q", "--filter", `ancestor=${imageName}`]),
|
|
1586
|
+
catch: () => new Error("Failed to list containers"),
|
|
1587
|
+
});
|
|
1588
|
+
|
|
1589
|
+
const containerIds = psResult.stdout.trim().split("\n").filter(Boolean);
|
|
1590
|
+
|
|
1591
|
+
for (const id of containerIds) {
|
|
1592
|
+
yield* Effect.tryPromise({
|
|
1593
|
+
try: () => execa("docker", ["stop", id]),
|
|
1594
|
+
catch: () => new Error(`Failed to stop container ${id}`),
|
|
1595
|
+
}).pipe(Effect.catchAll(() => Effect.void));
|
|
1596
|
+
stopped.push(id);
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
return {
|
|
1601
|
+
status: "stopped" as const,
|
|
1602
|
+
stopped,
|
|
1603
|
+
};
|
|
1604
|
+
});
|
|
1605
|
+
|
|
1606
|
+
try {
|
|
1607
|
+
return await Effect.runPromise(dockerEffect);
|
|
1608
|
+
} catch (error) {
|
|
1609
|
+
return {
|
|
1610
|
+
status: "error" as const,
|
|
1611
|
+
stopped: [],
|
|
1612
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
1613
|
+
};
|
|
1614
|
+
}
|
|
1615
|
+
}),
|
|
1371
1616
|
}),
|
|
1372
1617
|
});
|
|
1618
|
+
|
|
1619
|
+
|
package/src/types.ts
CHANGED
|
@@ -9,6 +9,9 @@ export const HostConfigSchema = z.object({
|
|
|
9
9
|
development: z.string(),
|
|
10
10
|
production: z.string(),
|
|
11
11
|
secrets: z.array(z.string()).optional(),
|
|
12
|
+
template: z.string().optional(),
|
|
13
|
+
files: z.array(z.string()).optional(),
|
|
14
|
+
sync: z.lazy(() => SyncConfigSchema).optional(),
|
|
12
15
|
});
|
|
13
16
|
export type HostConfig = z.infer<typeof HostConfigSchema>;
|
|
14
17
|
|
|
@@ -21,6 +24,9 @@ export const RemoteConfigSchema = z.object({
|
|
|
21
24
|
exposes: z.record(z.string(), z.string()).optional(),
|
|
22
25
|
variables: z.record(z.string(), z.string()).optional(),
|
|
23
26
|
secrets: z.array(z.string()).optional(),
|
|
27
|
+
template: z.string().optional(),
|
|
28
|
+
files: z.array(z.string()).optional(),
|
|
29
|
+
sync: z.lazy(() => SyncConfigSchema).optional(),
|
|
24
30
|
});
|
|
25
31
|
export type RemoteConfig = z.infer<typeof RemoteConfigSchema>;
|
|
26
32
|
|
|
@@ -38,11 +44,18 @@ export const SharedDepConfigSchema = z.object({
|
|
|
38
44
|
});
|
|
39
45
|
export type SharedDepConfig = z.infer<typeof SharedDepConfigSchema>;
|
|
40
46
|
|
|
47
|
+
export const SyncConfigSchema = z.object({
|
|
48
|
+
scripts: z.union([z.array(z.string()), z.literal(true)]).optional(),
|
|
49
|
+
dependencies: z.boolean().default(true),
|
|
50
|
+
devDependencies: z.boolean().default(true),
|
|
51
|
+
});
|
|
52
|
+
export type SyncConfig = z.infer<typeof SyncConfigSchema>;
|
|
53
|
+
|
|
41
54
|
export const BosConfigSchema = z.object({
|
|
42
55
|
account: z.string(),
|
|
56
|
+
testnet: z.string().optional(),
|
|
43
57
|
gateway: GatewayConfigSchema,
|
|
44
|
-
|
|
45
|
-
create: z.record(z.string(), z.string()).optional(),
|
|
58
|
+
template: z.string().optional(),
|
|
46
59
|
cli: z.object({
|
|
47
60
|
version: z.string().optional(),
|
|
48
61
|
}).optional(),
|