@treeseed/cli 0.8.6 → 0.8.7

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.
@@ -88,6 +88,7 @@ export type ConfigViewportLayout = UiViewportLayout & {
88
88
  inputHeight: number;
89
89
  actionRowHeight: number;
90
90
  };
91
+ export declare function resolveCurrentConfigValue(context: ConfigContextSnapshot, overrides: Record<string, string>, entryId: string, scope?: ConfigScope): string;
91
92
  export declare function filterCliConfigPages(pages: ConfigPage[], query: string): ConfigPage[];
92
93
  export declare function computeConfigViewportLayout(rows: number, columns: number): ConfigViewportLayout;
93
94
  export declare function buildCliConfigPages(context: ConfigContextSnapshot, selectedFilter: ConfigScope, overrides?: Record<string, string>, viewMode?: ConfigViewMode): ConfigPage[];
@@ -21,6 +21,12 @@ import {
21
21
  } from "../ui/framework.js";
22
22
  import { useTerminalMouse } from "../ui/mouse.js";
23
23
  const FULL_CONFIG_FILTERS = ["local", "staging", "prod"];
24
+ function firstAvailableScope(context, preferred = "local") {
25
+ if (context.scopes.includes(preferred)) {
26
+ return preferred;
27
+ }
28
+ return context.scopes[0] ?? preferred;
29
+ }
24
30
  function maskValue(value) {
25
31
  if (!value) {
26
32
  return "(unset)";
@@ -111,7 +117,7 @@ function resolveCurrentConfigValue(context, overrides, entryId, scope = "local")
111
117
  return overrides[scopedOverrideKey] ?? "";
112
118
  }
113
119
  for (const candidateScope of [scope, ...context.scopes.filter((candidate) => candidate !== scope)]) {
114
- const entry = context.entriesByScope[candidateScope].find((candidate) => candidate.id === entryId);
120
+ const entry = context.entriesByScope[candidateScope]?.find((candidate) => candidate.id === entryId);
115
121
  if (entry?.storage === "shared") {
116
122
  const overrideKey = `shared:${entryId}`;
117
123
  if (overrideKey in overrides) {
@@ -236,15 +242,15 @@ function buildCliConfigPages(context, selectedFilter, overrides = {}, viewMode =
236
242
  const sharedEntries = /* @__PURE__ */ new Set();
237
243
  const pages = [];
238
244
  for (const scope of selectedScopes) {
239
- for (const entry of context.entriesByScope[scope]) {
245
+ for (const entry of context.entriesByScope[scope] ?? []) {
240
246
  if (entry.storage === "shared") {
241
247
  if (sharedEntries.has(entry.id)) {
242
248
  continue;
243
249
  }
244
- const relevantScopes = selectedScopes.filter((candidateScope) => context.entriesByScope[candidateScope].some((candidate) => candidate.id === entry.id));
250
+ const relevantScopes = selectedScopes.filter((candidateScope) => context.entriesByScope[candidateScope]?.some((candidate) => candidate.id === entry.id));
245
251
  const key2 = `shared:${entry.id}`;
246
252
  sharedEntries.add(entry.id);
247
- const requiredScopes = relevantScopes.filter((candidateScope) => context.entriesByScope[candidateScope].some((candidate) => candidate.id === entry.id && candidate.required));
253
+ const requiredScopes = relevantScopes.filter((candidateScope) => context.entriesByScope[candidateScope]?.some((candidate) => candidate.id === entry.id && candidate.required));
248
254
  const currentValue = resolveSharedEntryValue(relevantScopes, requiredScopes, context.entriesByScope, entry.id, "currentValue");
249
255
  const suggestedValue = resolveSharedEntryValue(relevantScopes, requiredScopes, context.entriesByScope, entry.id, "suggestedValue", {
250
256
  fallbackToRelevant: requiredScopes.length === 0
@@ -465,7 +471,10 @@ async function runCliConfigEditor(context, options = {}) {
465
471
  function App() {
466
472
  const sidebarFilterHeight = 4;
467
473
  const [currentContext, setCurrentContext] = React.useState(context);
468
- const [filterIndex, setFilterIndex] = React.useState(0);
474
+ const [filterIndex, setFilterIndex] = React.useState(() => {
475
+ const initialScope = firstAvailableScope(context);
476
+ return Math.max(0, FULL_CONFIG_FILTERS.indexOf(initialScope));
477
+ });
469
478
  const [viewMode, setViewMode] = React.useState(options.initialViewMode ?? "startup");
470
479
  const [pageIndex, setPageIndex] = React.useState(0);
471
480
  const [sidebarOffset, setSidebarOffset] = React.useState(0);
@@ -484,7 +493,8 @@ async function runCliConfigEditor(context, options = {}) {
484
493
  const { exit } = useApp();
485
494
  const windowSize = useWindowSize();
486
495
  const layout = computeConfigViewportLayout(windowSize?.rows ?? 24, windowSize?.columns ?? 100);
487
- const selectedFilter = FULL_CONFIG_FILTERS[filterIndex] ?? "local";
496
+ const selectedFilter = FULL_CONFIG_FILTERS[filterIndex] ?? firstAvailableScope(currentContext);
497
+ const readinessScope = firstAvailableScope(currentContext, selectedFilter);
488
498
  const allPages = buildCliConfigPages(currentContext, selectedFilter, overrides, viewMode);
489
499
  const pages = viewMode === "full" ? filterCliConfigPages(allPages, filterQuery) : allPages;
490
500
  const safePageIndex = pages.length === 0 ? 0 : Math.min(pageIndex, pages.length - 1);
@@ -510,10 +520,10 @@ async function runCliConfigEditor(context, options = {}) {
510
520
  const detailWidth = viewMode === "full" ? layout.contentWidth : layout.columns;
511
521
  const detailPanel = detailViewportLines(detailSourceLines, detailWidth, layout.detailHeight, detailOffset);
512
522
  const configReadiness = {
513
- github: { configured: hasUsableValue(resolveCurrentConfigValue(currentContext, overrides, "GH_TOKEN", "local")) },
514
- cloudflare: { configured: hasUsableValue(resolveCurrentConfigValue(currentContext, overrides, "CLOUDFLARE_API_TOKEN", "local")) },
515
- railway: { configured: hasUsableValue(resolveCurrentConfigValue(currentContext, overrides, "RAILWAY_API_TOKEN", "local")) },
516
- localDevelopment: currentContext.configReadinessByScope.local?.localDevelopment ?? { configured: true }
523
+ github: { configured: hasUsableValue(resolveCurrentConfigValue(currentContext, overrides, "GH_TOKEN", readinessScope)) },
524
+ cloudflare: { configured: hasUsableValue(resolveCurrentConfigValue(currentContext, overrides, "CLOUDFLARE_API_TOKEN", readinessScope)) },
525
+ railway: { configured: hasUsableValue(resolveCurrentConfigValue(currentContext, overrides, "RAILWAY_API_TOKEN", readinessScope)) },
526
+ localDevelopment: currentContext.configReadinessByScope[readinessScope]?.localDevelopment ?? { configured: true }
517
527
  };
518
528
  const sidebarHeight = Math.max(4, layout.bodyHeight - sidebarFilterHeight);
519
529
  const sidebarViewportSize = Math.max(1, sidebarHeight - 4);
@@ -1171,5 +1181,6 @@ export {
1171
1181
  filterCliConfigPages,
1172
1182
  normalizeConfigInputChunk,
1173
1183
  readLinuxClipboardText,
1184
+ resolveCurrentConfigValue,
1174
1185
  runCliConfigEditor
1175
1186
  };
@@ -117,8 +117,12 @@ function renderConfigResult(commandName, result) {
117
117
  const payload = result.payload;
118
118
  const toolHealth = payload.toolHealth;
119
119
  const configContext = payload.context;
120
- const configReadiness = configContext?.configReadinessByScope?.local ?? {};
121
120
  const readinessByScope = payload.result?.readinessByScope ?? {};
121
+ const configReadinessByScope = configContext?.configReadinessByScope ?? {};
122
+ const providerConfigStatus = (provider) => {
123
+ const selectedScopes = Array.isArray(payload.scopes) && payload.scopes.length > 0 ? payload.scopes : Object.keys(configReadinessByScope);
124
+ return selectedScopes.some((scope) => configReadinessByScope?.[scope]?.[provider]?.configured) ? "configured" : "missing";
125
+ };
122
126
  const bootstrapSystemsByScope = payload.result?.bootstrapSystemsByScope ?? payload.bootstrapSystemsByScope ?? {};
123
127
  const skippedSystems = Object.values(bootstrapSystemsByScope).flatMap((entry) => Array.isArray(entry?.skipped) ? entry.skipped : []);
124
128
  const resourceInventoryByScope = payload.result?.resourceInventoryByScope ?? payload.resourceInventoryByScope ?? {};
@@ -149,9 +153,9 @@ function renderConfigResult(commandName, result) {
149
153
  { label: "Prod readiness", value: configReadinessLabel(readinessByScope.prod) },
150
154
  { label: "Pages project", value: resourceInventoryByScope.staging?.resources?.pagesProject ?? resourceInventoryByScope.prod?.resources?.pagesProject ?? "(unset)" },
151
155
  { label: "R2 bucket", value: resourceInventoryByScope.staging?.resources?.contentBucket ?? resourceInventoryByScope.prod?.resources?.contentBucket ?? "(unset)" },
152
- { label: "GitHub token/config", value: configReadiness.github?.configured ? "configured" : "missing" },
153
- { label: "Cloudflare token/config", value: configReadiness.cloudflare?.configured ? "configured" : "missing" },
154
- { label: "Railway token/config", value: configReadiness.railway?.configured ? "configured" : "missing" },
156
+ { label: "GitHub token/config", value: providerConfigStatus("github") },
157
+ { label: "Cloudflare token/config", value: providerConfigStatus("cloudflare") },
158
+ { label: "Railway token/config", value: providerConfigStatus("railway") },
155
159
  { label: "GitHub CLI", value: toolHealth?.githubCli?.available ? "ready" : "missing" },
156
160
  { label: "Wrangler CLI", value: toolHealth?.wranglerCli?.available ? "ready" : "missing" },
157
161
  { label: "Railway CLI", value: toolHealth?.railwayCli?.available ? "ready" : "missing" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@treeseed/cli",
3
- "version": "0.8.6",
3
+ "version": "0.8.7",
4
4
  "description": "Operator-facing Treeseed CLI package.",
5
5
  "license": "AGPL-3.0-only",
6
6
  "repository": {
@@ -45,7 +45,7 @@
45
45
  "release:publish": "node ./scripts/run-ts.mjs ./scripts/publish-package.ts"
46
46
  },
47
47
  "dependencies": {
48
- "@treeseed/sdk": "0.8.6",
48
+ "@treeseed/sdk": "0.8.7",
49
49
  "ink": "^7.0.0",
50
50
  "react": "^19.2.5"
51
51
  },