@finebi/finebi-cli 1.6.0 → 1.6.2
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 +16 -12
- package/package.json +1 -1
- package/skills/finebi-cli-agent/SKILL.md +43 -123
- package/skills/finebi-cli-agent/agents/openai.yaml +1 -1
- package/skills/finebi-cli-agent/references/artifact-ordering.md +3 -3
- package/skills/finebi-cli-agent/references/command-workflows.md +31 -2
- package/skills/finebi-cli-agent/references/dashboard-data-result.md +191 -191
- package/skills/finebi-cli-agent/references/dashboard-dsl-api.md +44 -44
- package/skills/finebi-cli-agent/references/dsl-handoff-contract.md +1 -1
- package/skills/finebi-cli-agent/references/handoff-contract.md +6 -4
- package/skills/finebi-cli-agent/references/mutation-confirmation-contract.md +2 -0
- package/skills/finebi-cli-agent/references/scenario-workflows.md +10 -8
- package/skills/finebi-cli-agent/references/web-app.md +12 -6
- package/skills/finebi-cli-agent/references/widget-dsl-core.md +6 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/package.json +1 -1
- package/skills/finebi-cli-agent/scripts/finebi-cli/src/app.js +157 -189
- package/skills/finebi-cli-agent/scripts/finebi-cli/src/browser-auth.js +3 -4
- package/skills/finebi-cli-agent/scripts/finebi-cli/src/config.js +28 -28
- package/skills/finebi-cli-agent/scripts/finebi-cli/src/error-catalog.js +2 -2
- package/skills/finebi-cli-agent/scripts/finebi-cli/src/help.js +65 -61
- package/skills/finebi-cli-agent/scripts/finebi-cli/src/install-skill.js +89 -89
- package/skills/finebi-cli-agent/scripts/finebi-cli/src/profiles.js +158 -158
- package/skills/finebi-cli-agent/scripts/finebi-cli/src/skill-files.js +94 -94
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/AGENTS.md +23 -7
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/README.md +19 -6
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/docs/acceptance.md +80 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/docs/auth-and-deploy.md +19 -4
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/docs/data-contract.md +48 -3
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/docs/design.md +43 -2
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/docs/filters.md +6 -2
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/examples/cockpit.config.json +22 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/examples/retail.config.json +185 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/index.html +2 -2
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/package-lock.json +2 -2
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/package.json +3 -2
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/public/app-config.json +11 -11
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/scripts/browser-session.mjs +59 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/scripts/dev-proxy.mjs +26 -24
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/scripts/dev-session.mjs +57 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/scripts/session-file.mjs +45 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/App.tsx +63 -31
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/cockpit.css +3 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/components/Chart.tsx +40 -31
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/components/DataTable.tsx +12 -5
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/components/FilterBar.tsx +16 -10
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/components/Insights.tsx +17 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/components/MonthPicker.tsx +32 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/components/OptionFilter.tsx +7 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/components/RangeFilter.tsx +9 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/components/Widget.tsx +40 -28
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/components/calendar-locales.ts +3 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/components/calendar.ts +14 -14
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/components/charts/Bar.tsx +6 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/components/charts/Line.tsx +6 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/components/charts/Pie.tsx +6 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/components/month-picker.css +1 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/finebi/client.ts +32 -11
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/finebi/config.d.ts +3 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/finebi/config.js +58 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/finebi/date.d.ts +1 -1
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/finebi/date.js +5 -5
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/finebi/filters.d.ts +4 -4
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/finebi/filters.js +106 -106
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/finebi/normalize.ts +235 -77
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/finebi/types.ts +20 -18
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/main.tsx +5 -5
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/retail.css +13 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/services/presentation.ts +36 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/services/table-data.ts +47 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/services/view-data.ts +18 -18
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/services/widget-queries.ts +18 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/tests/browser/fixtures.mjs +28 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/tests/browser/index.html +16 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/tests/browser/main.tsx +149 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/tests/browser/test-page.css +4 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/tests/browser/vite.config.mjs +13 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/tests/bundle.test.mjs +19 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/tests/cockpit.test.mjs +73 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/tests/field-selection.test.mjs +70 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/tests/proxy.test.mjs +31 -4
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/tests/retail.test.mjs +31 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/tests/runtime.test.mjs +8 -1
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/tests/session.test.mjs +91 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/tests/table-normalize.test.mjs +189 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/tests/vite-auth.test.mjs +41 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/tsconfig.json +4 -4
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/vite.config.ts +11 -4
- package/skills/finebi-cli-agent/scripts/finebi-cli-runner.mjs +4 -4
- package/src/app.js +157 -189
- package/src/browser-auth.js +3 -4
- package/src/config.js +28 -28
- package/src/error-catalog.js +2 -2
- package/src/help.js +65 -61
- package/src/install-skill.js +89 -89
- package/src/profiles.js +158 -158
- package/src/skill-files.js +94 -94
- package/templates/web-app/AGENTS.md +23 -7
- package/templates/web-app/README.md +19 -6
- package/templates/web-app/docs/acceptance.md +80 -0
- package/templates/web-app/docs/auth-and-deploy.md +19 -4
- package/templates/web-app/docs/data-contract.md +48 -3
- package/templates/web-app/docs/design.md +43 -2
- package/templates/web-app/docs/filters.md +6 -2
- package/templates/web-app/examples/cockpit.config.json +22 -0
- package/templates/web-app/examples/retail.config.json +185 -0
- package/templates/web-app/index.html +2 -2
- package/templates/web-app/package-lock.json +2 -2
- package/templates/web-app/package.json +3 -2
- package/templates/web-app/public/app-config.json +11 -11
- package/templates/web-app/scripts/browser-session.mjs +59 -0
- package/templates/web-app/scripts/dev-proxy.mjs +26 -24
- package/templates/web-app/scripts/dev-session.mjs +57 -0
- package/templates/web-app/scripts/session-file.mjs +45 -0
- package/templates/web-app/src/App.tsx +63 -31
- package/templates/web-app/src/cockpit.css +3 -0
- package/templates/web-app/src/components/Chart.tsx +40 -31
- package/templates/web-app/src/components/DataTable.tsx +12 -5
- package/templates/web-app/src/components/FilterBar.tsx +16 -10
- package/templates/web-app/src/components/Insights.tsx +17 -0
- package/templates/web-app/src/components/MonthPicker.tsx +32 -0
- package/templates/web-app/src/components/OptionFilter.tsx +7 -0
- package/templates/web-app/src/components/RangeFilter.tsx +9 -0
- package/templates/web-app/src/components/Widget.tsx +40 -28
- package/templates/web-app/src/components/calendar-locales.ts +3 -0
- package/templates/web-app/src/components/calendar.ts +14 -14
- package/templates/web-app/src/components/charts/Bar.tsx +6 -0
- package/templates/web-app/src/components/charts/Line.tsx +6 -0
- package/templates/web-app/src/components/charts/Pie.tsx +6 -0
- package/templates/web-app/src/components/month-picker.css +1 -0
- package/templates/web-app/src/finebi/client.ts +32 -11
- package/templates/web-app/src/finebi/config.d.ts +3 -0
- package/templates/web-app/src/finebi/config.js +58 -0
- package/templates/web-app/src/finebi/date.d.ts +1 -1
- package/templates/web-app/src/finebi/date.js +5 -5
- package/templates/web-app/src/finebi/filters.d.ts +4 -4
- package/templates/web-app/src/finebi/filters.js +106 -106
- package/templates/web-app/src/finebi/normalize.ts +235 -77
- package/templates/web-app/src/finebi/types.ts +20 -18
- package/templates/web-app/src/main.tsx +5 -5
- package/templates/web-app/src/retail.css +13 -0
- package/templates/web-app/src/services/presentation.ts +36 -0
- package/templates/web-app/src/services/table-data.ts +47 -0
- package/templates/web-app/src/services/view-data.ts +18 -18
- package/templates/web-app/src/services/widget-queries.ts +18 -0
- package/templates/web-app/tests/browser/fixtures.mjs +28 -0
- package/templates/web-app/tests/browser/index.html +16 -0
- package/templates/web-app/tests/browser/main.tsx +149 -0
- package/templates/web-app/tests/browser/test-page.css +4 -0
- package/templates/web-app/tests/browser/vite.config.mjs +13 -0
- package/templates/web-app/tests/bundle.test.mjs +19 -0
- package/templates/web-app/tests/cockpit.test.mjs +73 -0
- package/templates/web-app/tests/field-selection.test.mjs +70 -0
- package/templates/web-app/tests/proxy.test.mjs +31 -4
- package/templates/web-app/tests/retail.test.mjs +31 -0
- package/templates/web-app/tests/runtime.test.mjs +8 -1
- package/templates/web-app/tests/session.test.mjs +91 -0
- package/templates/web-app/tests/table-normalize.test.mjs +189 -0
- package/templates/web-app/tests/vite-auth.test.mjs +41 -0
- package/templates/web-app/tsconfig.json +4 -4
- package/templates/web-app/vite.config.ts +11 -4
package/src/app.js
CHANGED
|
@@ -1,189 +1,157 @@
|
|
|
1
|
-
import {cp, mkdir, readdir, readFile, stat, writeFile} from "node:fs/promises";
|
|
2
|
-
import {dirname, resolve} from "node:path";
|
|
3
|
-
import {fileURLToPath} from "node:url";
|
|
4
|
-
|
|
5
|
-
import {cliError} from "./errors.js";
|
|
6
|
-
import {failure, success} from "./output.js";
|
|
7
|
-
|
|
8
|
-
const TEMPLATE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..", "templates", "web-app");
|
|
9
|
-
const NAME_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
|
|
10
|
-
const WINDOWS_DEVICE_NAME = /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])$/iu;
|
|
11
|
-
|
|
12
|
-
export async function handleApp(args, context = {}) {
|
|
13
|
-
const action = args[0];
|
|
14
|
-
if (action === "create") return createApp(args.slice(1), context);
|
|
15
|
-
if (action === "inspect") return inspectApp(args.slice(1));
|
|
16
|
-
return invalid("app", "App command is required.", ["finebi-cli app create --name <name> --path <parent>", "finebi-cli app inspect --path <project>"]);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export async function createApp(args, context = {}) {
|
|
20
|
-
const options = parseOptions(args, "app.create");
|
|
21
|
-
if (!options.ok) return options.error;
|
|
22
|
-
const {name, path: parentPath} = options.value;
|
|
23
|
-
if (!name || name.length > 214 || !NAME_PATTERN.test(name) || WINDOWS_DEVICE_NAME.test(name)) {
|
|
24
|
-
return invalid("app.create", "name must be a simple lowercase npm name (letters, digits, and single hyphens).", ["finebi-cli app create --name sales-dashboard --path <parent>"]);
|
|
25
|
-
}
|
|
26
|
-
if (!parentPath) {
|
|
27
|
-
return invalid("app.create", "path must be an existing parent directory.");
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
await
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if (
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const
|
|
81
|
-
if (
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
await
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
if (!
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
if (widget[field] !== undefined && !nonEmptyString(widget[field])) errors.push(`widgets[${index}].${field} must be a non-empty string.`);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
try {
|
|
162
|
-
const {validateFilterBindings} = await import("../templates/web-app/src/finebi/filters.js");
|
|
163
|
-
validateFilterBindings(config.filters);
|
|
164
|
-
} catch (error) {errors.push(error.message);}
|
|
165
|
-
return errors;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function nonEmptyString(value) {
|
|
169
|
-
return typeof value === "string" && value.trim().length > 0;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
function parseOptions(args, command) {
|
|
173
|
-
const value = {};
|
|
174
|
-
for (let index = 0; index < args.length; index++) {
|
|
175
|
-
const option = args[index];
|
|
176
|
-
if (option === "--json") continue;
|
|
177
|
-
if (option !== "--name" && option !== "--path") return {ok: false, error: invalid(command, `Unsupported option: ${option}`)};
|
|
178
|
-
const key = option.slice(2);
|
|
179
|
-
const next = args[++index];
|
|
180
|
-
if (!next || next.startsWith("--")) return {ok: false, error: invalid(command, `${option} requires a value.`)};
|
|
181
|
-
if (value[key]) return {ok: false, error: invalid(command, `${option} must only be supplied once.`)};
|
|
182
|
-
value[key] = next;
|
|
183
|
-
}
|
|
184
|
-
return {ok: true, value};
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
function invalid(command, message, nextActions = []) {
|
|
188
|
-
return failure(command, cliError("INVALID_ARGUMENT", {path: command, message, nextActions}));
|
|
189
|
-
}
|
|
1
|
+
import {cp, mkdir, readdir, readFile, stat, writeFile} from "node:fs/promises";
|
|
2
|
+
import {dirname, resolve} from "node:path";
|
|
3
|
+
import {fileURLToPath} from "node:url";
|
|
4
|
+
|
|
5
|
+
import {cliError} from "./errors.js";
|
|
6
|
+
import {failure, success} from "./output.js";
|
|
7
|
+
|
|
8
|
+
const TEMPLATE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..", "templates", "web-app");
|
|
9
|
+
const NAME_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
|
|
10
|
+
const WINDOWS_DEVICE_NAME = /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])$/iu;
|
|
11
|
+
|
|
12
|
+
export async function handleApp(args, context = {}) {
|
|
13
|
+
const action = args[0];
|
|
14
|
+
if (action === "create") return createApp(args.slice(1), context);
|
|
15
|
+
if (action === "inspect") return inspectApp(args.slice(1));
|
|
16
|
+
return invalid("app", "App command is required.", ["finebi-cli app create --name <name> --path <parent>", "finebi-cli app inspect --path <project>"]);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function createApp(args, context = {}) {
|
|
20
|
+
const options = parseOptions(args, "app.create");
|
|
21
|
+
if (!options.ok) return options.error;
|
|
22
|
+
const {name, path: parentPath} = options.value;
|
|
23
|
+
if (!name || name.length > 214 || !NAME_PATTERN.test(name) || WINDOWS_DEVICE_NAME.test(name)) {
|
|
24
|
+
return invalid("app.create", "name must be a simple lowercase npm name (letters, digits, and single hyphens).", ["finebi-cli app create --name sales-dashboard --path <parent>"]);
|
|
25
|
+
}
|
|
26
|
+
if (!parentPath) {
|
|
27
|
+
return invalid("app.create", "path must be an existing parent directory.");
|
|
28
|
+
}
|
|
29
|
+
if (options.value.layout && !['overview','workbench','cockpit','retail'].includes(options.value.layout)) return invalid('app.create','layout must be overview, workbench, cockpit or retail.');
|
|
30
|
+
const parent = resolve(parentPath);
|
|
31
|
+
const destination = resolve(parent, name);
|
|
32
|
+
if (dirname(destination) !== parent) {
|
|
33
|
+
return invalid("app.create", "name must stay inside the selected parent directory.");
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
if (!(await stat(parent)).isDirectory()) return invalid("app.create", "path must point to a directory.");
|
|
37
|
+
} catch (error) {
|
|
38
|
+
return invalid("app.create", error.code === "ENOENT" ? `Parent directory does not exist: ${parent}` : `Cannot inspect parent directory: ${error.message}`);
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
const templateRoot = context.templateRoot ? resolve(context.templateRoot) : TEMPLATE_ROOT;
|
|
42
|
+
const templatePackage = JSON.parse(await readFile(resolve(templateRoot, "package.json"), "utf8"));
|
|
43
|
+
const templateConfigPath = resolve(templateRoot, "public", "app-config.json");
|
|
44
|
+
let config = {schemaVersion: "1", title: name, basePath: "/webroot/decision", layout: "overview", dashboardId: "", maxRows: 1000, pageSize: 8, widgets: [], filters: []};
|
|
45
|
+
try {
|
|
46
|
+
config = JSON.parse(await readFile(templateConfigPath, "utf8"));
|
|
47
|
+
} catch (error) {
|
|
48
|
+
if (error.code !== "ENOENT") throw error;
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
await mkdir(destination, {recursive: false});
|
|
52
|
+
} catch (error) {
|
|
53
|
+
return invalid("app.create", error.code === "EEXIST" ? `Destination already exists: ${destination}` : `Cannot create destination: ${error.message}`);
|
|
54
|
+
}
|
|
55
|
+
await copyTemplateContents(templateRoot, destination);
|
|
56
|
+
if(options.value.layout) config.layout=options.value.layout;
|
|
57
|
+
if(config.layout==='cockpit') config.insights=true;
|
|
58
|
+
await mkdir(resolve(destination, "public"), {recursive: true});
|
|
59
|
+
await writeFile(resolve(destination, "package.json"), `${JSON.stringify({...templatePackage, name}, null, 2)}\n`, "utf8");
|
|
60
|
+
await writeFile(resolve(destination, "public", "app-config.json"), `${JSON.stringify({...config, templateVersion: templatePackage.version}, null, 2)}\n`, "utf8");
|
|
61
|
+
const lockPath = resolve(destination, "package-lock.json");
|
|
62
|
+
try {
|
|
63
|
+
const lock = JSON.parse(await readFile(lockPath, "utf8"));
|
|
64
|
+
lock.name = name;
|
|
65
|
+
if (lock.packages?.[""]) lock.packages[""].name = name;
|
|
66
|
+
await writeFile(lockPath, `${JSON.stringify(lock, null, 2)}\n`, "utf8");
|
|
67
|
+
} catch (error) {
|
|
68
|
+
if (error.code !== "ENOENT") throw error;
|
|
69
|
+
}
|
|
70
|
+
return success("app.create", {name, path: destination, templateVersion: templatePackage.version});
|
|
71
|
+
} catch (error) {
|
|
72
|
+
return invalid("app.create", `Could not create project: ${error.message}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function copyTemplateContents(sourceRoot, destinationRoot, relativeDirectory = "") {
|
|
77
|
+
const sourceDirectory = resolve(sourceRoot, relativeDirectory);
|
|
78
|
+
const destinationDirectory = resolve(destinationRoot, relativeDirectory);
|
|
79
|
+
for (const entry of (await readdir(sourceDirectory, {withFileTypes: true})).sort((left, right) => left.name.localeCompare(right.name, "en"))) {
|
|
80
|
+
const relativePath = `${relativeDirectory}/${entry.name}`.replace(/^\//u, "");
|
|
81
|
+
if (!templatePathAllowed(relativePath)) continue;
|
|
82
|
+
const sourcePath = resolve(sourceDirectory, entry.name);
|
|
83
|
+
const destinationPath = resolve(destinationDirectory, entry.name);
|
|
84
|
+
if (entry.isDirectory()) {
|
|
85
|
+
await mkdir(destinationPath, {recursive: false});
|
|
86
|
+
await copyTemplateContents(sourceRoot, destinationRoot, relativePath);
|
|
87
|
+
} else if (entry.isFile()) {
|
|
88
|
+
await cp(sourcePath, destinationPath, {force: false, errorOnExist: true});
|
|
89
|
+
} else {
|
|
90
|
+
throw new Error(`Unsupported template entry: ${relativePath}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function templatePathAllowed(relative) {
|
|
96
|
+
const relativePath = relative.replaceAll("\\", "/");
|
|
97
|
+
const parts = relativePath.split("/").filter(Boolean);
|
|
98
|
+
const basename = parts.at(-1) || "";
|
|
99
|
+
return !parts.some(part => ["node_modules", "dist", "temp", ".git"].includes(part))
|
|
100
|
+
&& basename !== ".env"
|
|
101
|
+
&& (!basename.startsWith(".env.") || basename === ".env.example");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function inspectApp(args) {
|
|
105
|
+
const options = parseOptions(args, "app.inspect");
|
|
106
|
+
if (!options.ok) return options.error;
|
|
107
|
+
const projectPath = options.value.path;
|
|
108
|
+
if (!projectPath) return invalid("app.inspect", "path must be a project directory.");
|
|
109
|
+
const project = resolve(projectPath);
|
|
110
|
+
try {
|
|
111
|
+
if (!(await stat(project)).isDirectory()) return invalid("app.inspect", "path must point to a project directory.");
|
|
112
|
+
const configPath = resolve(project, "public", "app-config.json");
|
|
113
|
+
const config = JSON.parse(await readFile(configPath, "utf8"));
|
|
114
|
+
if (!config || typeof config !== "object" || Array.isArray(config)) return invalid("app.inspect", "app-config.json must contain a JSON object.");
|
|
115
|
+
if (options.value.strict) {
|
|
116
|
+
const {configErrors}=await import('../templates/web-app/src/finebi/config.js');
|
|
117
|
+
const errors = configErrors(config);
|
|
118
|
+
if (errors.length) return failure("app.inspect", cliError("INVALID_ARGUMENT", {path: "public/app-config.json", message: errors.join(" ")}));
|
|
119
|
+
}
|
|
120
|
+
return success("app.inspect", {
|
|
121
|
+
path: project,
|
|
122
|
+
dashboardId: config.dashboardId ?? null,
|
|
123
|
+
layout: config.layout ?? null,
|
|
124
|
+
directoryEntryId: config.directoryEntryId || null,
|
|
125
|
+
widgets: Array.isArray(config.widgets) ? config.widgets.length : null,
|
|
126
|
+
templateVersion: config.templateVersion || null,
|
|
127
|
+
configValidated: options.value.strict === true,
|
|
128
|
+
remoteValidated: false,
|
|
129
|
+
});
|
|
130
|
+
} catch (error) {
|
|
131
|
+
return invalid("app.inspect", `Cannot read project configuration: ${error.message}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function parseOptions(args, command) {
|
|
136
|
+
const value = {};
|
|
137
|
+
for (let index = 0; index < args.length; index++) {
|
|
138
|
+
const option = args[index];
|
|
139
|
+
if (option === "--json") continue;
|
|
140
|
+
if (option === "--strict" && command === "app.inspect") {
|
|
141
|
+
if (value.strict) return {ok: false, error: invalid(command, "--strict must only be supplied once.")};
|
|
142
|
+
value.strict = true;
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
if (!["--name","--path",...(command==='app.create'?['--layout']:[])].includes(option)) return {ok: false, error: invalid(command, `Unsupported option: ${option}`)};
|
|
146
|
+
const key = option.slice(2);
|
|
147
|
+
const next = args[++index];
|
|
148
|
+
if (!next || next.startsWith("--")) return {ok: false, error: invalid(command, `${option} requires a value.`)};
|
|
149
|
+
if (value[key]) return {ok: false, error: invalid(command, `${option} must only be supplied once.`)};
|
|
150
|
+
value[key] = next;
|
|
151
|
+
}
|
|
152
|
+
return {ok: true, value};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function invalid(command, message, nextActions = []) {
|
|
156
|
+
return failure(command, cliError("INVALID_ARGUMENT", {path: command, message, nextActions}));
|
|
157
|
+
}
|
package/src/browser-auth.js
CHANGED
|
@@ -160,9 +160,8 @@ function abortedAuthorization() {
|
|
|
160
160
|
function validateBaseUrl(value) {
|
|
161
161
|
try {
|
|
162
162
|
const url = new URL(value);
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
throw new Error("Browser authorization requires HTTPS, except for localhost development.");
|
|
163
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") {
|
|
164
|
+
throw new Error("Browser authorization requires an HTTP or HTTPS URL.");
|
|
166
165
|
}
|
|
167
166
|
if (url.username || url.password || url.search || url.hash) {
|
|
168
167
|
throw new Error("Browser authorization base URL must not contain credentials, a query, or a fragment.");
|
|
@@ -172,7 +171,7 @@ function validateBaseUrl(value) {
|
|
|
172
171
|
return failure("auth.login", cliError("BROWSER_AUTH_URL_INVALID", {
|
|
173
172
|
path: "baseUrl",
|
|
174
173
|
message: error instanceof Error ? error.message : String(error),
|
|
175
|
-
nextActions: ["finebi-cli config set base-url <https-url>"],
|
|
174
|
+
nextActions: ["finebi-cli config set base-url <http-or-https-url>"],
|
|
176
175
|
}));
|
|
177
176
|
}
|
|
178
177
|
}
|
package/src/config.js
CHANGED
|
@@ -8,7 +8,7 @@ import {CLI_CONFIG_DIRECTORY} from "./branding.js";
|
|
|
8
8
|
import {maskSecret} from "./output.js";
|
|
9
9
|
|
|
10
10
|
export const DEFAULT_TIMEOUT_MS = 30000;
|
|
11
|
-
export const DEFAULT_CAPABILITY_TTL_MS =
|
|
11
|
+
export const DEFAULT_CAPABILITY_TTL_MS = 20 * 60 * 60 * 1000;
|
|
12
12
|
|
|
13
13
|
export function defaultConfigPath(env = process.env) {
|
|
14
14
|
const home = env.USERPROFILE || env.HOME || homedir();
|
|
@@ -56,23 +56,23 @@ export function writeConfigAtomically(path, config, options = {}) {
|
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
export function resolveConfig(flags = {}, env = process.env, fileConfig = readConfig()) {
|
|
60
|
-
const baseUrl = firstNonEmpty(flags.baseUrl, env.FINEBI_BASE_URL, fileConfig.baseUrl);
|
|
61
|
-
const sameServer = sameBaseUrl(baseUrl, fileConfig.baseUrl);
|
|
62
|
-
const explicitToken = firstNonEmpty(flags.token, env.FINEBI_AUTH_TOKEN);
|
|
63
|
-
const savedAuth = sameServer ? fileConfig.auth : undefined;
|
|
64
|
-
const token = firstNonEmpty(explicitToken, savedAuth?.token, sameServer ? fileConfig.token : undefined);
|
|
59
|
+
export function resolveConfig(flags = {}, env = process.env, fileConfig = readConfig()) {
|
|
60
|
+
const baseUrl = firstNonEmpty(flags.baseUrl, env.FINEBI_BASE_URL, fileConfig.baseUrl);
|
|
61
|
+
const sameServer = sameBaseUrl(baseUrl, fileConfig.baseUrl);
|
|
62
|
+
const explicitToken = firstNonEmpty(flags.token, env.FINEBI_AUTH_TOKEN);
|
|
63
|
+
const savedAuth = sameServer ? fileConfig.auth : undefined;
|
|
64
|
+
const token = firstNonEmpty(explicitToken, savedAuth?.token, sameServer ? fileConfig.token : undefined);
|
|
65
65
|
return normalizeConfig({
|
|
66
66
|
...fileConfig,
|
|
67
|
-
baseUrl,
|
|
67
|
+
baseUrl,
|
|
68
68
|
timeoutMs: parsePositiveInt(firstNonEmpty(flags.timeoutMs, env.FINEBI_TIMEOUT_MS), fileConfig.timeoutMs),
|
|
69
69
|
capabilityTtlMs: parsePositiveInt(
|
|
70
70
|
firstNonEmpty(flags.capabilityTtlMs, env.FINEBI_CAPABILITY_TTL_MS),
|
|
71
71
|
fileConfig.capabilityTtlMs,
|
|
72
72
|
),
|
|
73
73
|
auth: {
|
|
74
|
-
...(explicitToken ? {} : savedAuth),
|
|
75
|
-
token,
|
|
74
|
+
...(explicitToken ? {} : savedAuth),
|
|
75
|
+
token,
|
|
76
76
|
},
|
|
77
77
|
});
|
|
78
78
|
}
|
|
@@ -81,10 +81,10 @@ export function updateConfigValue(config, key, value, now = () => new Date().toI
|
|
|
81
81
|
const next = normalizeConfig(config);
|
|
82
82
|
switch (key) {
|
|
83
83
|
case "base-url":
|
|
84
|
-
if (next.baseUrl !== value) {
|
|
85
|
-
next.capabilityCacheInvalidatedAt = now();
|
|
86
|
-
delete next.auth;
|
|
87
|
-
delete next.token;
|
|
84
|
+
if (next.baseUrl !== value) {
|
|
85
|
+
next.capabilityCacheInvalidatedAt = now();
|
|
86
|
+
delete next.auth;
|
|
87
|
+
delete next.token;
|
|
88
88
|
}
|
|
89
89
|
next.baseUrl = value;
|
|
90
90
|
return next;
|
|
@@ -128,7 +128,7 @@ function defaultConfig() {
|
|
|
128
128
|
};
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
-
export function normalizeConfig(config) {
|
|
131
|
+
export function normalizeConfig(config) {
|
|
132
132
|
return {
|
|
133
133
|
...config,
|
|
134
134
|
timeoutMs: parsePositiveInt(config.timeoutMs, DEFAULT_TIMEOUT_MS),
|
|
@@ -136,14 +136,14 @@ export function normalizeConfig(config) {
|
|
|
136
136
|
};
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
function sanitizeConfig(config) {
|
|
140
|
-
if (config.schemaVersion === 2) {
|
|
141
|
-
return {
|
|
142
|
-
schemaVersion: 2,
|
|
143
|
-
currentProfile: config.currentProfile,
|
|
144
|
-
profiles: Object.fromEntries(Object.entries(config.profiles).map(([name, profile]) => [name, sanitizeConfig(normalizeConfig(profile))])),
|
|
145
|
-
};
|
|
146
|
-
}
|
|
139
|
+
function sanitizeConfig(config) {
|
|
140
|
+
if (config.schemaVersion === 2) {
|
|
141
|
+
return {
|
|
142
|
+
schemaVersion: 2,
|
|
143
|
+
currentProfile: config.currentProfile,
|
|
144
|
+
profiles: Object.fromEntries(Object.entries(config.profiles).map(([name, profile]) => [name, sanitizeConfig(normalizeConfig(profile))])),
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
147
|
const { token: _legacyToken, ...rest } = config;
|
|
148
148
|
const auth = config.auth
|
|
149
149
|
? {
|
|
@@ -166,11 +166,11 @@ function sanitizeConfig(config) {
|
|
|
166
166
|
...rest,
|
|
167
167
|
auth,
|
|
168
168
|
};
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
export function sameBaseUrl(left, right) {
|
|
172
|
-
return (left || "").replace(/\/+$/, "") === (right || "").replace(/\/+$/, "");
|
|
173
|
-
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function sameBaseUrl(left, right) {
|
|
172
|
+
return (left || "").replace(/\/+$/, "") === (right || "").replace(/\/+$/, "");
|
|
173
|
+
}
|
|
174
174
|
|
|
175
175
|
function firstNonEmpty(...values) {
|
|
176
176
|
return values.find((value) => typeof value === "string" && value.trim().length > 0)?.trim();
|
package/src/error-catalog.js
CHANGED
|
@@ -19,8 +19,8 @@ export const ERROR_CATALOG = [
|
|
|
19
19
|
recoverableByAgent: true,
|
|
20
20
|
defaultRetryable: false,
|
|
21
21
|
pathSemantics: "Browser authorization base URL",
|
|
22
|
-
canonicalNextActions: ["finebi-cli config set base-url <https-url>"],
|
|
23
|
-
expectedAgentBehaviors: ["configure an
|
|
22
|
+
canonicalNextActions: ["finebi-cli config set base-url <http-or-https-url>"],
|
|
23
|
+
expectedAgentBehaviors: ["configure an HTTP or HTTPS URL", "rerun browser login"],
|
|
24
24
|
benchmarkTags: ["auth", "browser", "url-validation"],
|
|
25
25
|
},
|
|
26
26
|
{
|