@finebi/finebi-cli 1.6.2 → 2.0.0
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 -1
- package/agent-installation-guide.md +70 -0
- package/package.json +2 -1
- 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/scripts/finebi-cli/package.json +1 -1
- package/skills/finebi-cli-agent/scripts/finebi-cli/src/app.js +160 -157
- package/skills/finebi-cli-agent/scripts/finebi-cli/src/config.js +27 -27
- package/skills/finebi-cli-agent/scripts/finebi-cli/src/help.js +55 -55
- 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 +95 -94
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/docs/acceptance.md +80 -80
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/examples/cockpit.config.json +22 -22
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/examples/retail.config.json +185 -185
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/gitignore.template +6 -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/public/app-config.json +11 -11
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/App.tsx +63 -63
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/cockpit.css +3 -3
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/components/Chart.tsx +40 -40
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/components/Insights.tsx +17 -17
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/components/MonthPicker.tsx +32 -32
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/components/OptionFilter.tsx +7 -7
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/components/RangeFilter.tsx +9 -9
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/components/Widget.tsx +40 -40
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/components/calendar-locales.ts +3 -3
- 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 -6
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/components/charts/Line.tsx +6 -6
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/components/charts/Pie.tsx +6 -6
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/components/month-picker.css +1 -1
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/finebi/config.d.ts +3 -3
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/finebi/config.js +58 -58
- 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 -235
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/finebi/types.ts +20 -20
- 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 +12 -12
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/services/presentation.ts +36 -36
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/services/table-data.ts +47 -47
- 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 -18
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/tests/browser/fixtures.mjs +28 -28
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/tests/browser/index.html +16 -16
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/tests/browser/main.tsx +149 -149
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/tests/browser/test-page.css +4 -4
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/tests/browser/vite.config.mjs +13 -13
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/tests/bundle.test.mjs +19 -19
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/tests/cockpit.test.mjs +73 -73
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/tests/field-selection.test.mjs +70 -70
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/tests/retail.test.mjs +31 -31
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/tests/table-normalize.test.mjs +189 -189
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/tsconfig.json +4 -4
- package/skills/finebi-cli-agent/scripts/finebi-cli-runner.mjs +4 -4
- package/src/app.js +160 -157
- package/src/config.js +27 -27
- package/src/help.js +55 -55
- package/src/install-skill.js +89 -89
- package/src/profiles.js +158 -158
- package/src/skill-files.js +95 -94
- package/templates/web-app/docs/acceptance.md +80 -80
- package/templates/web-app/examples/cockpit.config.json +22 -22
- package/templates/web-app/examples/retail.config.json +185 -185
- package/templates/web-app/gitignore.template +6 -0
- package/templates/web-app/index.html +2 -2
- package/templates/web-app/public/app-config.json +11 -11
- package/templates/web-app/src/App.tsx +63 -63
- package/templates/web-app/src/cockpit.css +3 -3
- package/templates/web-app/src/components/Chart.tsx +40 -40
- package/templates/web-app/src/components/Insights.tsx +17 -17
- package/templates/web-app/src/components/MonthPicker.tsx +32 -32
- package/templates/web-app/src/components/OptionFilter.tsx +7 -7
- package/templates/web-app/src/components/RangeFilter.tsx +9 -9
- package/templates/web-app/src/components/Widget.tsx +40 -40
- package/templates/web-app/src/components/calendar-locales.ts +3 -3
- package/templates/web-app/src/components/calendar.ts +14 -14
- package/templates/web-app/src/components/charts/Bar.tsx +6 -6
- package/templates/web-app/src/components/charts/Line.tsx +6 -6
- package/templates/web-app/src/components/charts/Pie.tsx +6 -6
- package/templates/web-app/src/components/month-picker.css +1 -1
- package/templates/web-app/src/finebi/config.d.ts +3 -3
- package/templates/web-app/src/finebi/config.js +58 -58
- 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 -235
- package/templates/web-app/src/finebi/types.ts +20 -20
- package/templates/web-app/src/main.tsx +5 -5
- package/templates/web-app/src/retail.css +12 -12
- package/templates/web-app/src/services/presentation.ts +36 -36
- package/templates/web-app/src/services/table-data.ts +47 -47
- package/templates/web-app/src/services/view-data.ts +18 -18
- package/templates/web-app/src/services/widget-queries.ts +18 -18
- package/templates/web-app/tests/browser/fixtures.mjs +28 -28
- package/templates/web-app/tests/browser/index.html +16 -16
- package/templates/web-app/tests/browser/main.tsx +149 -149
- package/templates/web-app/tests/browser/test-page.css +4 -4
- package/templates/web-app/tests/browser/vite.config.mjs +13 -13
- package/templates/web-app/tests/bundle.test.mjs +19 -19
- package/templates/web-app/tests/cockpit.test.mjs +73 -73
- package/templates/web-app/tests/field-selection.test.mjs +70 -70
- package/templates/web-app/tests/retail.test.mjs +31 -31
- package/templates/web-app/tests/table-normalize.test.mjs +189 -189
- package/templates/web-app/tsconfig.json +4 -4
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {"target":"ES2020","lib":["ES2020","DOM","DOM.Iterable"],"module":"ESNext","moduleResolution":"Bundler","jsx":"react-jsx","strict":true,"skipLibCheck":true,"esModuleInterop":true,"resolveJsonModule":true,"isolatedModules":true,"noEmit":true},
|
|
3
|
-
"include":["src","vite.config.ts"]
|
|
4
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {"target":"ES2020","lib":["ES2020","DOM","DOM.Iterable"],"module":"ESNext","moduleResolution":"Bundler","jsx":"react-jsx","strict":true,"skipLibCheck":true,"esModuleInterop":true,"resolveJsonModule":true,"isolatedModules":true,"noEmit":true},
|
|
3
|
+
"include":["src","vite.config.ts"]
|
|
4
|
+
}
|
|
@@ -10,10 +10,10 @@ const scriptDirectory = dirname(fileURLToPath(import.meta.url));
|
|
|
10
10
|
const bundledCliEntry = resolve(scriptDirectory, "finebi-cli", "src", "index.js");
|
|
11
11
|
|
|
12
12
|
export async function run(argv = process.argv.slice(2), options = {}) {
|
|
13
|
-
const env = {...(options.env ?? process.env)};
|
|
14
|
-
if (!env.FINEBI_CLI_PATH?.trim() && existsSync(bundledCliEntry)) {
|
|
15
|
-
env.FINEBI_CLI_PATH = bundledCliEntry;
|
|
16
|
-
}
|
|
13
|
+
const env = {...(options.env ?? process.env)};
|
|
14
|
+
if (!env.FINEBI_CLI_PATH?.trim() && existsSync(bundledCliEntry)) {
|
|
15
|
+
env.FINEBI_CLI_PATH = bundledCliEntry;
|
|
16
|
+
}
|
|
17
17
|
return runCore(argv, {...options, env});
|
|
18
18
|
}
|
|
19
19
|
|
package/src/app.js
CHANGED
|
@@ -1,157 +1,160 @@
|
|
|
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
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
await
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
return
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
if (
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
await
|
|
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
|
-
if (!
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
path:
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
if (!
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
+
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 gitignore = await readFile(resolve(templateRoot, "gitignore.template"), "utf8");
|
|
44
|
+
const templateConfigPath = resolve(templateRoot, "public", "app-config.json");
|
|
45
|
+
let config = {schemaVersion: "1", title: name, basePath: "/webroot/decision", layout: "overview", dashboardId: "", maxRows: 1000, pageSize: 8, widgets: [], filters: []};
|
|
46
|
+
try {
|
|
47
|
+
config = JSON.parse(await readFile(templateConfigPath, "utf8"));
|
|
48
|
+
} catch (error) {
|
|
49
|
+
if (error.code !== "ENOENT") throw error;
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
await mkdir(destination, {recursive: false});
|
|
53
|
+
} catch (error) {
|
|
54
|
+
return invalid("app.create", error.code === "EEXIST" ? `Destination already exists: ${destination}` : `Cannot create destination: ${error.message}`);
|
|
55
|
+
}
|
|
56
|
+
await copyTemplateContents(templateRoot, destination);
|
|
57
|
+
await writeFile(resolve(destination, ".gitignore"), gitignore, "utf8");
|
|
58
|
+
if(options.value.layout) config.layout=options.value.layout;
|
|
59
|
+
if(config.layout==='cockpit') config.insights=true;
|
|
60
|
+
await mkdir(resolve(destination, "public"), {recursive: true});
|
|
61
|
+
await writeFile(resolve(destination, "package.json"), `${JSON.stringify({...templatePackage, name}, null, 2)}\n`, "utf8");
|
|
62
|
+
await writeFile(resolve(destination, "public", "app-config.json"), `${JSON.stringify({...config, templateVersion: templatePackage.version}, null, 2)}\n`, "utf8");
|
|
63
|
+
const lockPath = resolve(destination, "package-lock.json");
|
|
64
|
+
try {
|
|
65
|
+
const lock = JSON.parse(await readFile(lockPath, "utf8"));
|
|
66
|
+
lock.name = name;
|
|
67
|
+
if (lock.packages?.[""]) lock.packages[""].name = name;
|
|
68
|
+
await writeFile(lockPath, `${JSON.stringify(lock, null, 2)}\n`, "utf8");
|
|
69
|
+
} catch (error) {
|
|
70
|
+
if (error.code !== "ENOENT") throw error;
|
|
71
|
+
}
|
|
72
|
+
return success("app.create", {name, path: destination, templateVersion: templatePackage.version});
|
|
73
|
+
} catch (error) {
|
|
74
|
+
return invalid("app.create", `Could not create project: ${error.message}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function copyTemplateContents(sourceRoot, destinationRoot, relativeDirectory = "") {
|
|
79
|
+
const sourceDirectory = resolve(sourceRoot, relativeDirectory);
|
|
80
|
+
const destinationDirectory = resolve(destinationRoot, relativeDirectory);
|
|
81
|
+
for (const entry of (await readdir(sourceDirectory, {withFileTypes: true})).sort((left, right) => left.name.localeCompare(right.name, "en"))) {
|
|
82
|
+
const relativePath = `${relativeDirectory}/${entry.name}`.replace(/^\//u, "");
|
|
83
|
+
if (relativePath === ".gitignore" || relativePath === "gitignore.template") continue;
|
|
84
|
+
if (!templatePathAllowed(relativePath)) continue;
|
|
85
|
+
const sourcePath = resolve(sourceDirectory, entry.name);
|
|
86
|
+
const destinationPath = resolve(destinationDirectory, entry.name);
|
|
87
|
+
if (entry.isDirectory()) {
|
|
88
|
+
await mkdir(destinationPath, {recursive: false});
|
|
89
|
+
await copyTemplateContents(sourceRoot, destinationRoot, relativePath);
|
|
90
|
+
} else if (entry.isFile()) {
|
|
91
|
+
await cp(sourcePath, destinationPath, {force: false, errorOnExist: true});
|
|
92
|
+
} else {
|
|
93
|
+
throw new Error(`Unsupported template entry: ${relativePath}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function templatePathAllowed(relative) {
|
|
99
|
+
const relativePath = relative.replaceAll("\\", "/");
|
|
100
|
+
const parts = relativePath.split("/").filter(Boolean);
|
|
101
|
+
const basename = parts.at(-1) || "";
|
|
102
|
+
return !parts.some(part => ["node_modules", "dist", "temp", ".git"].includes(part))
|
|
103
|
+
&& basename !== ".env"
|
|
104
|
+
&& (!basename.startsWith(".env.") || basename === ".env.example");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function inspectApp(args) {
|
|
108
|
+
const options = parseOptions(args, "app.inspect");
|
|
109
|
+
if (!options.ok) return options.error;
|
|
110
|
+
const projectPath = options.value.path;
|
|
111
|
+
if (!projectPath) return invalid("app.inspect", "path must be a project directory.");
|
|
112
|
+
const project = resolve(projectPath);
|
|
113
|
+
try {
|
|
114
|
+
if (!(await stat(project)).isDirectory()) return invalid("app.inspect", "path must point to a project directory.");
|
|
115
|
+
const configPath = resolve(project, "public", "app-config.json");
|
|
116
|
+
const config = JSON.parse(await readFile(configPath, "utf8"));
|
|
117
|
+
if (!config || typeof config !== "object" || Array.isArray(config)) return invalid("app.inspect", "app-config.json must contain a JSON object.");
|
|
118
|
+
if (options.value.strict) {
|
|
119
|
+
const {configErrors}=await import('../templates/web-app/src/finebi/config.js');
|
|
120
|
+
const errors = configErrors(config);
|
|
121
|
+
if (errors.length) return failure("app.inspect", cliError("INVALID_ARGUMENT", {path: "public/app-config.json", message: errors.join(" ")}));
|
|
122
|
+
}
|
|
123
|
+
return success("app.inspect", {
|
|
124
|
+
path: project,
|
|
125
|
+
dashboardId: config.dashboardId ?? null,
|
|
126
|
+
layout: config.layout ?? null,
|
|
127
|
+
directoryEntryId: config.directoryEntryId || null,
|
|
128
|
+
widgets: Array.isArray(config.widgets) ? config.widgets.length : null,
|
|
129
|
+
templateVersion: config.templateVersion || null,
|
|
130
|
+
configValidated: options.value.strict === true,
|
|
131
|
+
remoteValidated: false,
|
|
132
|
+
});
|
|
133
|
+
} catch (error) {
|
|
134
|
+
return invalid("app.inspect", `Cannot read project configuration: ${error.message}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function parseOptions(args, command) {
|
|
139
|
+
const value = {};
|
|
140
|
+
for (let index = 0; index < args.length; index++) {
|
|
141
|
+
const option = args[index];
|
|
142
|
+
if (option === "--json") continue;
|
|
143
|
+
if (option === "--strict" && command === "app.inspect") {
|
|
144
|
+
if (value.strict) return {ok: false, error: invalid(command, "--strict must only be supplied once.")};
|
|
145
|
+
value.strict = true;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (!["--name","--path",...(command==='app.create'?['--layout']:[])].includes(option)) return {ok: false, error: invalid(command, `Unsupported option: ${option}`)};
|
|
149
|
+
const key = option.slice(2);
|
|
150
|
+
const next = args[++index];
|
|
151
|
+
if (!next || next.startsWith("--")) return {ok: false, error: invalid(command, `${option} requires a value.`)};
|
|
152
|
+
if (value[key]) return {ok: false, error: invalid(command, `${option} must only be supplied once.`)};
|
|
153
|
+
value[key] = next;
|
|
154
|
+
}
|
|
155
|
+
return {ok: true, value};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function invalid(command, message, nextActions = []) {
|
|
159
|
+
return failure(command, cliError("INVALID_ARGUMENT", {path: command, message, nextActions}));
|
|
160
|
+
}
|
package/src/config.js
CHANGED
|
@@ -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();
|