@growthub/cli 0.9.5 → 0.9.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.
@@ -1,11 +1,12 @@
1
1
  import { promises as fs } from "node:fs";
2
2
  import path from "node:path";
3
3
  import { readAdapterConfig } from "@/lib/adapters/env";
4
-
5
- const KNOWN_FIELDS = ["dashboards", "widgetTypes", "canvas"];
6
- const KNOWN_WIDGET_KINDS = ["chart", "view", "iframe", "rich-text"];
7
- const GRID_COLUMNS = 12;
8
- const GRID_ROWS = 16;
4
+ import {
5
+ GRID_COLUMNS,
6
+ GRID_ROWS,
7
+ KNOWN_WIDGET_KINDS,
8
+ validateWorkspaceConfig
9
+ } from "@/lib/workspace-schema";
9
10
 
10
11
  function resolveWorkspaceConfigPath() {
11
12
  return path.resolve(/*turbopackIgnore: true*/ process.cwd(), "growthub.config.json");
@@ -36,156 +37,35 @@ function describePersistenceMode() {
36
37
  return { mode: "filesystem", reason: "Local development" };
37
38
  }
38
39
 
39
- function isFiniteInt(value) {
40
- return typeof value === "number" && Number.isFinite(value) && Math.floor(value) === value;
41
- }
42
-
43
- function validateWidgetArray(widgets, contextPath, errors, seenIds) {
44
- if (!Array.isArray(widgets)) {
45
- errors.push(`${contextPath} must be an array`);
46
- return;
47
- }
48
- const occupied = new Map();
49
- widgets.forEach((widget, index) => {
50
- const prefix = `${contextPath}[${index}]`;
51
- if (!widget || typeof widget !== "object" || Array.isArray(widget)) {
52
- errors.push(`${prefix} must be an object`);
53
- return;
54
- }
55
- if (typeof widget.id !== "string" || !widget.id) {
56
- errors.push(`${prefix}.id must be a non-empty string`);
57
- } else if (seenIds.has(widget.id)) {
58
- errors.push(`${prefix}.id duplicates an earlier widget id`);
59
- } else {
60
- seenIds.add(widget.id);
61
- }
62
- if (!KNOWN_WIDGET_KINDS.includes(widget.kind)) {
63
- errors.push(`${prefix}.kind must be one of ${KNOWN_WIDGET_KINDS.join(", ")}`);
64
- }
65
- if (!widget.position || typeof widget.position !== "object" || Array.isArray(widget.position)) {
66
- errors.push(`${prefix}.position must be an object`);
67
- return;
68
- }
69
- for (const k of ["x", "y", "w", "h"]) {
70
- if (!isFiniteInt(widget.position[k])) {
71
- errors.push(`${prefix}.position.${k} must be a finite integer`);
72
- }
73
- }
74
- if (
75
- isFiniteInt(widget.position.x) &&
76
- isFiniteInt(widget.position.w) &&
77
- (widget.position.x < 0 || widget.position.w < 1 || widget.position.x + widget.position.w > GRID_COLUMNS)
78
- ) {
79
- errors.push(`${prefix} x/w out of [0..${GRID_COLUMNS}] grid`);
80
- }
81
- if (
82
- isFiniteInt(widget.position.y) &&
83
- isFiniteInt(widget.position.h) &&
84
- (widget.position.y < 0 || widget.position.h < 1 || widget.position.y + widget.position.h > GRID_ROWS)
85
- ) {
86
- errors.push(`${prefix} y/h out of [0..${GRID_ROWS}] grid`);
87
- }
88
- if (
89
- isFiniteInt(widget.position.x) &&
90
- isFiniteInt(widget.position.y) &&
91
- isFiniteInt(widget.position.w) &&
92
- isFiniteInt(widget.position.h)
93
- ) {
94
- for (let dx = 0; dx < widget.position.w; dx += 1) {
95
- for (let dy = 0; dy < widget.position.h; dy += 1) {
96
- const cell = `${widget.position.x + dx}:${widget.position.y + dy}`;
97
- const previous = occupied.get(cell);
98
- if (previous) {
99
- errors.push(`${prefix} overlaps ${previous} at grid cell ${cell}`);
100
- } else {
101
- occupied.set(cell, `${prefix}.position`);
102
- }
103
- }
104
- }
105
- }
106
- });
107
- }
108
-
109
- function validateWorkspaceConfig(nextConfig) {
110
- if (!nextConfig || typeof nextConfig !== "object" || Array.isArray(nextConfig)) {
111
- const error = new Error("workspace config must be a plain object");
112
- error.code = "INVALID_WORKSPACE_CONFIG";
113
- error.details = ["root must be a plain object"];
114
- throw error;
115
- }
116
- const errors = [];
117
- for (const key of Object.keys(nextConfig)) {
118
- if (!KNOWN_FIELDS.includes(key)) {
119
- errors.push(`unknown top-level field: ${key}`);
120
- }
121
- }
122
- if (nextConfig.dashboards !== undefined && !Array.isArray(nextConfig.dashboards)) {
123
- errors.push("dashboards must be an array");
124
- }
125
- if (nextConfig.widgetTypes !== undefined && !Array.isArray(nextConfig.widgetTypes)) {
126
- errors.push("widgetTypes must be an array");
127
- }
128
- if (nextConfig.canvas !== undefined) {
129
- if (typeof nextConfig.canvas !== "object" || Array.isArray(nextConfig.canvas) || nextConfig.canvas === null) {
130
- errors.push("canvas must be a plain object");
131
- } else {
132
- const seenWidgetIds = new Set();
133
- if (nextConfig.canvas.widgets !== undefined) {
134
- validateWidgetArray(nextConfig.canvas.widgets, "canvas.widgets", errors, seenWidgetIds);
135
- }
136
- if (nextConfig.canvas.tabs !== undefined) {
137
- if (!Array.isArray(nextConfig.canvas.tabs)) {
138
- errors.push("canvas.tabs must be an array");
139
- } else {
140
- const seenTabIds = new Set();
141
- nextConfig.canvas.tabs.forEach((tab, index) => {
142
- const tabPrefix = `canvas.tabs[${index}]`;
143
- if (!tab || typeof tab !== "object" || Array.isArray(tab)) {
144
- errors.push(`${tabPrefix} must be an object`);
145
- return;
146
- }
147
- if (typeof tab.id !== "string" || !tab.id) {
148
- errors.push(`${tabPrefix}.id must be a non-empty string`);
149
- } else if (seenTabIds.has(tab.id)) {
150
- errors.push(`${tabPrefix}.id duplicates an earlier tab id`);
151
- } else {
152
- seenTabIds.add(tab.id);
153
- }
154
- if (typeof tab.name !== "string" || !tab.name) {
155
- errors.push(`${tabPrefix}.name must be a non-empty string`);
156
- }
157
- if (tab.widgets !== undefined) {
158
- validateWidgetArray(tab.widgets, `${tabPrefix}.widgets`, errors, seenWidgetIds);
159
- }
160
- });
161
- }
162
- }
163
- if (nextConfig.canvas.activeTabId !== undefined && typeof nextConfig.canvas.activeTabId !== "string") {
164
- errors.push("canvas.activeTabId must be a string");
165
- }
166
- }
167
- }
168
- if (errors.length) {
169
- const error = new Error(`invalid workspace config: ${errors.join("; ")}`);
170
- error.code = "INVALID_WORKSPACE_CONFIG";
171
- error.details = errors;
172
- throw error;
173
- }
174
- }
175
-
176
40
  function applyPatch(currentConfig, patch) {
177
41
  const next = { ...currentConfig };
178
42
  if (patch.dashboards !== undefined) next.dashboards = patch.dashboards;
179
43
  if (patch.widgetTypes !== undefined) next.widgetTypes = patch.widgetTypes;
180
44
  if (patch.canvas !== undefined && patch.canvas !== null) {
45
+ const patchCanvas = { ...patch.canvas };
46
+ if (Array.isArray(patchCanvas.tabs)) {
47
+ delete patchCanvas.widgets;
48
+ delete patchCanvas.name;
49
+ } else if (Array.isArray(patchCanvas.widgets)) {
50
+ delete patchCanvas.tabs;
51
+ delete patchCanvas.activeTabId;
52
+ }
181
53
  next.canvas = {
182
54
  ...currentConfig.canvas,
183
- ...patch.canvas,
184
- layout: { ...(currentConfig.canvas?.layout || {}), ...(patch.canvas.layout || {}) },
185
- bindings: { ...(currentConfig.canvas?.bindings || {}), ...(patch.canvas.bindings || {}) }
55
+ ...patchCanvas,
56
+ layout: { ...(currentConfig.canvas?.layout || {}), ...(patchCanvas.layout || {}) },
57
+ bindings: { ...(currentConfig.canvas?.bindings || {}), ...(patchCanvas.bindings || {}) }
186
58
  };
59
+ if (Array.isArray(patch.canvas.tabs)) {
60
+ delete next.canvas.widgets;
61
+ delete next.canvas.name;
62
+ }
63
+ if (Array.isArray(patch.canvas.widgets)) {
64
+ delete next.canvas.tabs;
65
+ delete next.canvas.activeTabId;
66
+ }
187
67
  for (const key of ["widgets", "tabs", "activeTabId", "name"]) {
188
- if (Object.prototype.hasOwnProperty.call(patch.canvas, key) && patch.canvas[key] === null) {
68
+ if (Object.prototype.hasOwnProperty.call(patchCanvas, key) && patchCanvas[key] === null) {
189
69
  delete next.canvas[key];
190
70
  }
191
71
  }