pilotswarm-cli 0.1.14 → 0.1.16
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 +3 -0
- package/bin/tui.js +8 -2
- package/node_modules/pilotswarm-ui-core/README.md +6 -0
- package/node_modules/pilotswarm-ui-core/package.json +32 -0
- package/node_modules/pilotswarm-ui-core/src/commands.js +72 -0
- package/node_modules/pilotswarm-ui-core/src/context-usage.js +212 -0
- package/node_modules/pilotswarm-ui-core/src/controller.js +3676 -0
- package/node_modules/pilotswarm-ui-core/src/formatting.js +872 -0
- package/node_modules/pilotswarm-ui-core/src/history.js +589 -0
- package/node_modules/pilotswarm-ui-core/src/index.js +13 -0
- package/node_modules/pilotswarm-ui-core/src/layout.js +196 -0
- package/node_modules/pilotswarm-ui-core/src/reducer.js +1030 -0
- package/node_modules/pilotswarm-ui-core/src/selectors.js +2921 -0
- package/node_modules/pilotswarm-ui-core/src/session-tree.js +109 -0
- package/node_modules/pilotswarm-ui-core/src/state.js +80 -0
- package/node_modules/pilotswarm-ui-core/src/store.js +23 -0
- package/node_modules/pilotswarm-ui-core/src/system-titles.js +24 -0
- package/node_modules/pilotswarm-ui-core/src/themes/catppuccin-mocha.js +56 -0
- package/node_modules/pilotswarm-ui-core/src/themes/cobalt2.js +56 -0
- package/node_modules/pilotswarm-ui-core/src/themes/dark-high-contrast.js +56 -0
- package/node_modules/pilotswarm-ui-core/src/themes/dracula.js +56 -0
- package/node_modules/pilotswarm-ui-core/src/themes/github-dark.js +56 -0
- package/node_modules/pilotswarm-ui-core/src/themes/gruvbox-dark.js +56 -0
- package/node_modules/pilotswarm-ui-core/src/themes/hacker-x-matrix.js +56 -0
- package/node_modules/pilotswarm-ui-core/src/themes/hacker-x-orion-prime.js +56 -0
- package/node_modules/pilotswarm-ui-core/src/themes/helpers.js +77 -0
- package/node_modules/pilotswarm-ui-core/src/themes/index.js +44 -0
- package/node_modules/pilotswarm-ui-core/src/themes/noctis-obscuro.js +56 -0
- package/node_modules/pilotswarm-ui-core/src/themes/noctis-viola.js +56 -0
- package/node_modules/pilotswarm-ui-core/src/themes/noctis.js +56 -0
- package/node_modules/pilotswarm-ui-core/src/themes/nord.js +56 -0
- package/node_modules/pilotswarm-ui-core/src/themes/solarized-dark.js +56 -0
- package/node_modules/pilotswarm-ui-core/src/themes/tokyo-night.js +56 -0
- package/node_modules/pilotswarm-ui-react/README.md +5 -0
- package/node_modules/pilotswarm-ui-react/package.json +36 -0
- package/node_modules/pilotswarm-ui-react/src/components.js +1413 -0
- package/node_modules/pilotswarm-ui-react/src/index.js +4 -0
- package/node_modules/pilotswarm-ui-react/src/platform.js +15 -0
- package/node_modules/pilotswarm-ui-react/src/use-controller-state.js +38 -0
- package/node_modules/pilotswarm-ui-react/src/web-app.js +2759 -0
- package/package.json +9 -3
- package/src/app.js +44 -0
- package/src/bootstrap-env.js +1 -37
- package/src/node-sdk-transport.js +555 -62
- package/src/platform.js +43 -11
- package/src/plugin-config.js +239 -0
- package/src/portal.js +7 -0
- package/src/sync-workspace-ui.js +53 -0
package/src/platform.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { spawnSync } from "node:child_process";
|
|
3
3
|
import { Box, Text } from "ink";
|
|
4
|
-
import { DEFAULT_THEME_ID, getTheme, parseTerminalMarkupRuns } from "pilotswarm-ui-core";
|
|
4
|
+
import { DEFAULT_THEME_ID, getTheme, isThemeLight, parseTerminalMarkupRuns } from "pilotswarm-ui-core";
|
|
5
5
|
|
|
6
6
|
const MAX_PROMPT_INPUT_ROWS = 3;
|
|
7
7
|
const SELECTION_BACKGROUND = "selectionBackground";
|
|
@@ -38,8 +38,12 @@ function resolveColorToken(color) {
|
|
|
38
38
|
return theme?.tui?.[color] || color;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
export function shouldDimGrayTextForTheme(theme = getCurrentTheme()) {
|
|
42
|
+
return !isThemeLight(theme);
|
|
43
|
+
}
|
|
44
|
+
|
|
41
45
|
function isDimColorToken(color) {
|
|
42
|
-
return color === "gray";
|
|
46
|
+
return color === "gray" && shouldDimGrayTextForTheme();
|
|
43
47
|
}
|
|
44
48
|
|
|
45
49
|
function trimText(value, width) {
|
|
@@ -361,19 +365,46 @@ function renderPanelRow(line, rowKey, contentWidth, borderColor, scrollIndicator
|
|
|
361
365
|
: selectedRuns.length > 0
|
|
362
366
|
? renderInlineRuns(selectedRuns, `inline:${rowKey}`)
|
|
363
367
|
: React.createElement(Text, null, "")),
|
|
364
|
-
React.createElement(Text, {
|
|
368
|
+
React.createElement(Text, {
|
|
369
|
+
color: scrollIndicator ? resolveColorToken("gray") : undefined,
|
|
370
|
+
dimColor: Boolean(scrollIndicator) && shouldDimGrayTextForTheme(),
|
|
371
|
+
}, scrollChar),
|
|
365
372
|
React.createElement(Text, { color: resolveColorToken(borderColor) }, "│"));
|
|
366
373
|
}
|
|
367
374
|
|
|
368
|
-
function renderBorderTop(title, color, width) {
|
|
375
|
+
function renderBorderTop(title, color, width, titleRight = null) {
|
|
369
376
|
const safeWidth = Math.max(8, Number(width) || 40);
|
|
370
|
-
const
|
|
371
|
-
const
|
|
377
|
+
const normalizedTitleRuns = normalizeTitleRuns(title, color);
|
|
378
|
+
const normalizedRightRuns = titleRight ? normalizeTitleRuns(titleRight, "gray") : [];
|
|
379
|
+
let safeTitleRuns = trimRuns(normalizedTitleRuns, Math.max(1, safeWidth - 6));
|
|
380
|
+
let safeRightRuns = trimRuns(normalizedRightRuns, Math.max(0, safeWidth - 8));
|
|
381
|
+
let titleWidth = titleRunLength(safeTitleRuns);
|
|
382
|
+
let rightWidth = titleRunLength(safeRightRuns);
|
|
383
|
+
const hasRightTitle = rightWidth > 0;
|
|
384
|
+
const chromeWidth = hasRightTitle ? 6 : 5;
|
|
385
|
+
const availableTitleWidth = Math.max(1, safeWidth - chromeWidth);
|
|
386
|
+
|
|
387
|
+
if (titleWidth + rightWidth > availableTitleWidth) {
|
|
388
|
+
safeTitleRuns = trimRuns(normalizedTitleRuns, Math.max(1, availableTitleWidth - rightWidth));
|
|
389
|
+
titleWidth = titleRunLength(safeTitleRuns);
|
|
390
|
+
if (titleWidth + rightWidth > availableTitleWidth) {
|
|
391
|
+
safeRightRuns = trimRuns(normalizedRightRuns, Math.max(0, availableTitleWidth - titleWidth));
|
|
392
|
+
rightWidth = titleRunLength(safeRightRuns);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const fill = Math.max(0, safeWidth - titleWidth - rightWidth - chromeWidth);
|
|
372
397
|
|
|
373
398
|
return React.createElement(Box, null,
|
|
374
399
|
React.createElement(Text, { color: resolveColorToken(color) }, "╭─ "),
|
|
375
400
|
renderInlineRuns(safeTitleRuns, "title"),
|
|
376
|
-
|
|
401
|
+
hasRightTitle
|
|
402
|
+
? [
|
|
403
|
+
React.createElement(Text, { key: "title-fill", color: resolveColorToken(color) }, ` ${"─".repeat(fill)} `),
|
|
404
|
+
...renderInlineRuns(safeRightRuns, "title-right"),
|
|
405
|
+
React.createElement(Text, { key: "title-end", color: resolveColorToken(color) }, "╮"),
|
|
406
|
+
]
|
|
407
|
+
: React.createElement(Text, { color: resolveColorToken(color) }, ` ${"─".repeat(fill)}╮`));
|
|
377
408
|
}
|
|
378
409
|
|
|
379
410
|
function renderBorderBottom(color, width) {
|
|
@@ -576,11 +607,12 @@ function Header({ title, subtitle }) {
|
|
|
576
607
|
justifyContent: "space-between",
|
|
577
608
|
},
|
|
578
609
|
React.createElement(Text, { bold: true, color: resolveColorToken("cyan") }, title),
|
|
579
|
-
React.createElement(Text, { color: resolveColorToken("gray"), dimColor:
|
|
610
|
+
React.createElement(Text, { color: resolveColorToken("gray"), dimColor: shouldDimGrayTextForTheme() }, subtitle || ""));
|
|
580
611
|
}
|
|
581
612
|
|
|
582
613
|
function Panel({
|
|
583
614
|
title,
|
|
615
|
+
titleRight,
|
|
584
616
|
color = "white",
|
|
585
617
|
focused = false,
|
|
586
618
|
width,
|
|
@@ -649,7 +681,7 @@ function Panel({
|
|
|
649
681
|
flexGrow,
|
|
650
682
|
flexBasis,
|
|
651
683
|
},
|
|
652
|
-
renderBorderTop(title, borderColor, safeWidth),
|
|
684
|
+
renderBorderTop(title, borderColor, safeWidth, titleRight),
|
|
653
685
|
lines
|
|
654
686
|
? React.createElement(Box, { flexDirection: "column", flexGrow: 1, backgroundColor: fillColor || undefined },
|
|
655
687
|
[
|
|
@@ -773,7 +805,7 @@ function Input({ label, value, focused, placeholder, rows = 1, cursorIndex = 0 }
|
|
|
773
805
|
? [
|
|
774
806
|
renderPromptRow(placeholder || "Type a message and press Enter", focused ? 0 : null, {
|
|
775
807
|
color: resolveColorToken("gray"),
|
|
776
|
-
dimColor:
|
|
808
|
+
dimColor: shouldDimGrayTextForTheme(),
|
|
777
809
|
showCursor: Boolean(focused),
|
|
778
810
|
keyPrefix: "prompt-line:0",
|
|
779
811
|
prefix: labelPrefix,
|
|
@@ -800,7 +832,7 @@ function StatusLine({ left, right }) {
|
|
|
800
832
|
justifyContent: "space-between",
|
|
801
833
|
},
|
|
802
834
|
React.createElement(Text, { color: resolveColorToken("white") }, left || ""),
|
|
803
|
-
React.createElement(Text, { color: resolveColorToken("gray"), dimColor:
|
|
835
|
+
React.createElement(Text, { color: resolveColorToken("gray"), dimColor: shouldDimGrayTextForTheme() }, right || ""));
|
|
804
836
|
}
|
|
805
837
|
|
|
806
838
|
function Overlay({ children }) {
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const pkgRoot = path.resolve(__dirname, "..");
|
|
7
|
+
const defaultTuiSplashPath = path.join(pkgRoot, "tui-splash.txt");
|
|
8
|
+
|
|
9
|
+
function fileExists(filePath) {
|
|
10
|
+
try {
|
|
11
|
+
return fs.existsSync(filePath);
|
|
12
|
+
} catch {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function readOptionalTextFile(filePath) {
|
|
18
|
+
try {
|
|
19
|
+
return fs.readFileSync(filePath, "utf-8").trimEnd();
|
|
20
|
+
} catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function getObject(value) {
|
|
26
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
27
|
+
? value
|
|
28
|
+
: {};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function firstNonEmptyString(...values) {
|
|
32
|
+
for (const value of values) {
|
|
33
|
+
if (typeof value === "string" && value.trim()) {
|
|
34
|
+
return value.trim();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function resolveRelativePath(baseDir, relativePath) {
|
|
41
|
+
if (!baseDir || typeof relativePath !== "string" || !relativePath.trim()) return null;
|
|
42
|
+
const basePath = path.resolve(baseDir);
|
|
43
|
+
const filePath = path.resolve(basePath, relativePath);
|
|
44
|
+
if (filePath !== basePath && !filePath.startsWith(`${basePath}${path.sep}`)) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
return filePath;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function readRelativeTextFile(baseDir, relativePath) {
|
|
51
|
+
const filePath = resolveRelativePath(baseDir, relativePath);
|
|
52
|
+
if (!filePath) return null;
|
|
53
|
+
return readOptionalTextFile(filePath);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function resolveRelativeAssetFile(baseDir, relativePath) {
|
|
57
|
+
const filePath = resolveRelativePath(baseDir, relativePath);
|
|
58
|
+
if (!filePath || !fileExists(filePath)) return null;
|
|
59
|
+
return filePath;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function firstAssetUrl(...values) {
|
|
63
|
+
for (const value of values) {
|
|
64
|
+
if (typeof value !== "string" || !value.trim()) continue;
|
|
65
|
+
const trimmed = value.trim();
|
|
66
|
+
if (/^(https?:\/\/|\/|data:|blob:)/iu.test(trimmed)) {
|
|
67
|
+
return trimmed;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function resolvePortalAsset(baseDir, { file, url }) {
|
|
74
|
+
const directUrl = firstAssetUrl(url);
|
|
75
|
+
if (directUrl) {
|
|
76
|
+
return { filePath: null, publicUrl: directUrl };
|
|
77
|
+
}
|
|
78
|
+
const filePath = resolveRelativeAssetFile(baseDir, file);
|
|
79
|
+
if (!filePath) {
|
|
80
|
+
return { filePath: null, publicUrl: null };
|
|
81
|
+
}
|
|
82
|
+
return { filePath, publicUrl: null };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function readSplashValue(baseDir, config, fallback) {
|
|
86
|
+
if (typeof config?.splash === "string" && config.splash.trim()) {
|
|
87
|
+
return config.splash;
|
|
88
|
+
}
|
|
89
|
+
if (typeof config?.splashFile === "string" && config.splashFile.trim()) {
|
|
90
|
+
const fileText = readRelativeTextFile(baseDir, config.splashFile);
|
|
91
|
+
if (fileText != null) return fileText;
|
|
92
|
+
}
|
|
93
|
+
return fallback;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function getDefaultSplash() {
|
|
97
|
+
return readOptionalTextFile(defaultTuiSplashPath) || "{bold}{cyan-fg}PilotSwarm{/cyan-fg}{/bold}";
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function readPluginMetadata(pluginDir) {
|
|
101
|
+
if (!pluginDir) return null;
|
|
102
|
+
const pluginJsonPath = path.join(pluginDir, "plugin.json");
|
|
103
|
+
if (!fileExists(pluginJsonPath)) return null;
|
|
104
|
+
try {
|
|
105
|
+
return JSON.parse(fs.readFileSync(pluginJsonPath, "utf-8"));
|
|
106
|
+
} catch (error) {
|
|
107
|
+
throw new Error(`Failed to parse plugin metadata: ${pluginJsonPath}: ${error.message}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function getPluginDirsFromEnv() {
|
|
112
|
+
const envDirs = String(process.env.PLUGIN_DIRS || "")
|
|
113
|
+
.split(",")
|
|
114
|
+
.map((value) => value.trim())
|
|
115
|
+
.filter(Boolean)
|
|
116
|
+
.map((value) => path.resolve(value));
|
|
117
|
+
if (envDirs.length > 0) return envDirs;
|
|
118
|
+
|
|
119
|
+
const cwdPlugin = path.resolve(process.cwd(), "plugins");
|
|
120
|
+
if (fileExists(cwdPlugin)) return [cwdPlugin];
|
|
121
|
+
|
|
122
|
+
const bundledPlugin = path.join(pkgRoot, "plugins");
|
|
123
|
+
if (fileExists(bundledPlugin)) return [bundledPlugin];
|
|
124
|
+
|
|
125
|
+
return [];
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function resolveTuiBranding(pluginDir) {
|
|
129
|
+
const pluginMeta = readPluginMetadata(pluginDir);
|
|
130
|
+
const tui = pluginMeta?.tui;
|
|
131
|
+
const defaultSplash = getDefaultSplash();
|
|
132
|
+
if (!tui || typeof tui !== "object") {
|
|
133
|
+
return { title: "PilotSwarm", splash: defaultSplash };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const title = firstNonEmptyString(tui.title, "PilotSwarm") || "PilotSwarm";
|
|
137
|
+
const splash = readSplashValue(pluginDir, tui, defaultSplash);
|
|
138
|
+
return { title, splash };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function resolvePortalConfigBundleFromPluginDirs(pluginDirs = []) {
|
|
142
|
+
const defaultSplash = getDefaultSplash();
|
|
143
|
+
const defaults = {
|
|
144
|
+
branding: {
|
|
145
|
+
title: "PilotSwarm",
|
|
146
|
+
pageTitle: "PilotSwarm",
|
|
147
|
+
splash: defaultSplash,
|
|
148
|
+
logoUrl: null,
|
|
149
|
+
faviconUrl: null,
|
|
150
|
+
},
|
|
151
|
+
ui: {
|
|
152
|
+
loadingMessage: "Preparing your workspace",
|
|
153
|
+
loadingCopy: "Connecting the shared workspace and live session feeds...",
|
|
154
|
+
},
|
|
155
|
+
auth: {
|
|
156
|
+
provider: null,
|
|
157
|
+
providers: {},
|
|
158
|
+
signInTitle: "Sign in to PilotSwarm",
|
|
159
|
+
signInMessage: null,
|
|
160
|
+
signInLabel: "Sign In",
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
for (const pluginDir of pluginDirs) {
|
|
165
|
+
const absDir = path.resolve(pluginDir);
|
|
166
|
+
const pluginMeta = readPluginMetadata(absDir);
|
|
167
|
+
if (!pluginMeta) continue;
|
|
168
|
+
|
|
169
|
+
const portal = getObject(pluginMeta?.portal);
|
|
170
|
+
const portalBranding = getObject(portal.branding);
|
|
171
|
+
const portalUi = getObject(portal.ui);
|
|
172
|
+
const portalAuth = getObject(portal.auth);
|
|
173
|
+
const tui = getObject(pluginMeta?.tui);
|
|
174
|
+
|
|
175
|
+
const title = firstNonEmptyString(portalBranding.title, portal.title, tui.title, defaults.branding.title) || defaults.branding.title;
|
|
176
|
+
const pageTitle = firstNonEmptyString(portalBranding.pageTitle, portal.pageTitle, title, defaults.branding.pageTitle) || defaults.branding.pageTitle;
|
|
177
|
+
const splash = readSplashValue(
|
|
178
|
+
absDir,
|
|
179
|
+
portalBranding,
|
|
180
|
+
readSplashValue(absDir, portal, readSplashValue(absDir, tui, defaults.branding.splash)),
|
|
181
|
+
);
|
|
182
|
+
const logoAsset = resolvePortalAsset(absDir, {
|
|
183
|
+
file: firstNonEmptyString(portalBranding.logoFile, portal.logoFile),
|
|
184
|
+
url: firstNonEmptyString(portalBranding.logoUrl, portal.logoUrl),
|
|
185
|
+
});
|
|
186
|
+
const faviconAsset = resolvePortalAsset(absDir, {
|
|
187
|
+
file: firstNonEmptyString(portalBranding.faviconFile, portal.faviconFile, portalBranding.logoFile, portal.logoFile),
|
|
188
|
+
url: firstNonEmptyString(portalBranding.faviconUrl, portal.faviconUrl, portalBranding.logoUrl, portal.logoUrl),
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
const assetFiles = {};
|
|
192
|
+
const branding = {
|
|
193
|
+
title,
|
|
194
|
+
pageTitle,
|
|
195
|
+
splash,
|
|
196
|
+
logoUrl: logoAsset.publicUrl || null,
|
|
197
|
+
faviconUrl: faviconAsset.publicUrl || null,
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
if (logoAsset.filePath) {
|
|
201
|
+
assetFiles.logo = logoAsset.filePath;
|
|
202
|
+
branding.logoUrl = "/api/portal-assets/logo";
|
|
203
|
+
}
|
|
204
|
+
if (faviconAsset.filePath) {
|
|
205
|
+
assetFiles.favicon = faviconAsset.filePath;
|
|
206
|
+
branding.faviconUrl = "/api/portal-assets/favicon";
|
|
207
|
+
}
|
|
208
|
+
if (!branding.faviconUrl && branding.logoUrl) {
|
|
209
|
+
branding.faviconUrl = branding.logoUrl;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
portalConfig: {
|
|
214
|
+
branding,
|
|
215
|
+
ui: {
|
|
216
|
+
loadingMessage: firstNonEmptyString(portalUi.loadingMessage, portal.loadingMessage, defaults.ui.loadingMessage) || defaults.ui.loadingMessage,
|
|
217
|
+
loadingCopy: firstNonEmptyString(portalUi.loadingCopy, portal.loadingCopy, defaults.ui.loadingCopy) || defaults.ui.loadingCopy,
|
|
218
|
+
},
|
|
219
|
+
auth: {
|
|
220
|
+
provider: firstNonEmptyString(portalAuth.provider, portal.provider),
|
|
221
|
+
providers: getObject(portalAuth.providers),
|
|
222
|
+
signInTitle: firstNonEmptyString(portalAuth.signInTitle, portal.signInTitle, `Sign in to ${title}`) || `Sign in to ${title}`,
|
|
223
|
+
signInMessage: firstNonEmptyString(portalAuth.signInMessage, portal.signInMessage, defaults.auth.signInMessage),
|
|
224
|
+
signInLabel: firstNonEmptyString(portalAuth.signInLabel, defaults.auth.signInLabel) || defaults.auth.signInLabel,
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
assetFiles,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
portalConfig: defaults,
|
|
233
|
+
assetFiles: {},
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export function resolvePortalConfigFromPluginDirs(pluginDirs = []) {
|
|
238
|
+
return resolvePortalConfigBundleFromPluginDirs(pluginDirs).portalConfig;
|
|
239
|
+
}
|
package/src/portal.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const BUNDLED_UI_PACKAGES = ["pilotswarm-ui-core", "pilotswarm-ui-react"];
|
|
7
|
+
|
|
8
|
+
function copyTree(sourceDir, targetDir) {
|
|
9
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
10
|
+
for (const entry of fs.readdirSync(sourceDir, { withFileTypes: true })) {
|
|
11
|
+
const sourcePath = path.join(sourceDir, entry.name);
|
|
12
|
+
const targetPath = path.join(targetDir, entry.name);
|
|
13
|
+
if (entry.isDirectory()) {
|
|
14
|
+
copyTree(sourcePath, targetPath);
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
fs.copyFileSync(sourcePath, targetPath);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function syncBundledWorkspaceUiPackages({ cliPackageDir = path.resolve(__dirname, "..") } = {}) {
|
|
22
|
+
const packagesDir = path.resolve(cliPackageDir, "..");
|
|
23
|
+
const syncedPackages = [];
|
|
24
|
+
|
|
25
|
+
for (const packageName of BUNDLED_UI_PACKAGES) {
|
|
26
|
+
const workspaceDir = path.join(packagesDir, packageName.replace("pilotswarm-", ""));
|
|
27
|
+
const sourcePackageJson = path.join(workspaceDir, "package.json");
|
|
28
|
+
const sourceReadme = path.join(workspaceDir, "README.md");
|
|
29
|
+
const sourceSrcDir = path.join(workspaceDir, "src");
|
|
30
|
+
if (!fs.existsSync(sourcePackageJson) || !fs.existsSync(sourceSrcDir)) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const targetDir = path.join(cliPackageDir, "node_modules", packageName);
|
|
35
|
+
fs.rmSync(targetDir, { recursive: true, force: true });
|
|
36
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
37
|
+
fs.copyFileSync(sourcePackageJson, path.join(targetDir, "package.json"));
|
|
38
|
+
if (fs.existsSync(sourceReadme)) {
|
|
39
|
+
fs.copyFileSync(sourceReadme, path.join(targetDir, "README.md"));
|
|
40
|
+
}
|
|
41
|
+
copyTree(sourceSrcDir, path.join(targetDir, "src"));
|
|
42
|
+
syncedPackages.push(packageName);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return syncedPackages;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
49
|
+
const syncedPackages = syncBundledWorkspaceUiPackages();
|
|
50
|
+
if (syncedPackages.length > 0) {
|
|
51
|
+
console.log(`[sync-workspace-ui] synced ${syncedPackages.join(", ")}`);
|
|
52
|
+
}
|
|
53
|
+
}
|