@rglabs/butterfly 2.0.1
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/CLAUDE.md +201 -0
- package/README.md +371 -0
- package/dist/commands/add.d.ts +23 -0
- package/dist/commands/add.js +303 -0
- package/dist/commands/code.d.ts +11 -0
- package/dist/commands/code.js +72 -0
- package/dist/commands/create-object.d.ts +6 -0
- package/dist/commands/create-object.js +293 -0
- package/dist/commands/create-report.d.ts +6 -0
- package/dist/commands/create-report.js +154 -0
- package/dist/commands/diff.d.ts +4 -0
- package/dist/commands/diff.js +238 -0
- package/dist/commands/download.d.ts +4 -0
- package/dist/commands/download.js +374 -0
- package/dist/commands/layout.d.ts +12 -0
- package/dist/commands/layout.js +83 -0
- package/dist/commands/record.d.ts +21 -0
- package/dist/commands/record.js +483 -0
- package/dist/commands/run-poc.d.ts +3 -0
- package/dist/commands/run-poc.js +18 -0
- package/dist/commands/setup.d.ts +3 -0
- package/dist/commands/setup.js +66 -0
- package/dist/commands/start-poc.d.ts +3 -0
- package/dist/commands/start-poc.js +55 -0
- package/dist/commands/sync-docs.d.ts +3 -0
- package/dist/commands/sync-docs.js +27 -0
- package/dist/commands/translate.d.ts +13 -0
- package/dist/commands/translate.js +401 -0
- package/dist/commands/upload.d.ts +3 -0
- package/dist/commands/upload.js +150 -0
- package/dist/commands/workflow-info.d.ts +13 -0
- package/dist/commands/workflow-info.js +161 -0
- package/dist/components/ConflictResolver.d.ts +12 -0
- package/dist/components/ConflictResolver.js +77 -0
- package/dist/components/DiffView.d.ts +11 -0
- package/dist/components/DiffView.js +101 -0
- package/dist/components/DownloadProgress.d.ts +11 -0
- package/dist/components/DownloadProgress.js +29 -0
- package/dist/components/RecordPreview.d.ts +11 -0
- package/dist/components/RecordPreview.js +91 -0
- package/dist/components/SetupForm.d.ts +8 -0
- package/dist/components/SetupForm.js +56 -0
- package/dist/components/UploadProgress.d.ts +13 -0
- package/dist/components/UploadProgress.js +42 -0
- package/dist/diff/adapters/index.d.ts +8 -0
- package/dist/diff/adapters/index.js +18 -0
- package/dist/diff/adapters/objectsAdapter.d.ts +13 -0
- package/dist/diff/adapters/objectsAdapter.js +177 -0
- package/dist/diff/adapters/reportsAdapter.d.ts +14 -0
- package/dist/diff/adapters/reportsAdapter.js +212 -0
- package/dist/diff/adapters/types.d.ts +19 -0
- package/dist/diff/adapters/types.js +2 -0
- package/dist/diff/engine.d.ts +19 -0
- package/dist/diff/engine.js +57 -0
- package/dist/diff/types.d.ts +34 -0
- package/dist/diff/types.js +110 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +117 -0
- package/dist/types/index.d.ts +18 -0
- package/dist/types/index.js +2 -0
- package/dist/utils/api.d.ts +85 -0
- package/dist/utils/api.js +1031 -0
- package/dist/utils/auth.d.ts +4 -0
- package/dist/utils/auth.js +22 -0
- package/dist/utils/bfySplitter.d.ts +12 -0
- package/dist/utils/bfySplitter.js +151 -0
- package/dist/utils/docs.d.ts +16 -0
- package/dist/utils/docs.js +186 -0
- package/dist/utils/errorLogger.d.ts +6 -0
- package/dist/utils/errorLogger.js +29 -0
- package/dist/utils/files.d.ts +14 -0
- package/dist/utils/files.js +772 -0
- package/dist/utils/lockManager.d.ts +15 -0
- package/dist/utils/lockManager.js +126 -0
- package/dist/utils/resourceHandlers.d.ts +50 -0
- package/dist/utils/resourceHandlers.js +684 -0
- package/dist/utils/resourceMapping.d.ts +32 -0
- package/dist/utils/resourceMapping.js +210 -0
- package/dist/utils/singleResourceDownload.d.ts +14 -0
- package/dist/utils/singleResourceDownload.js +261 -0
- package/dist/utils/summaryGenerator.d.ts +2 -0
- package/dist/utils/summaryGenerator.js +183 -0
- package/dist/utils/uploadHandler.d.ts +31 -0
- package/dist/utils/uploadHandler.js +263 -0
- package/docs/AI_API.md +93 -0
- package/docs/CLAUDE.md +216 -0
- package/docs/PROJECT_SPECIFIC.md +1 -0
- package/docs/RECORD_COMMAND.md +262 -0
- package/docs/WORKFLOW_API.md +480 -0
- package/docs/bfy-splitting.md +126 -0
- package/docs/cli-commands.md +333 -0
- package/docs/examples/README.md +95 -0
- package/docs/examples/order-system.md +147 -0
- package/docs/examples/product-catalog.md +195 -0
- package/docs/examples/reports.md +187 -0
- package/docs/excel-export.md +216 -0
- package/docs/field-types/README.md +29 -0
- package/docs/field-types/calculated.md +147 -0
- package/docs/field-types/code-mappings.md +84 -0
- package/docs/field-types/custom.md +340 -0
- package/docs/object-specs/README.md +136 -0
- package/docs/object-specs/code-parameters.md +151 -0
- package/docs/object-specs/creating.md +203 -0
- package/docs/object-specs/js-code-examples.md +208 -0
- package/docs/object-specs/js-field-updates.md +168 -0
- package/docs/objects/README.md +89 -0
- package/docs/objects/creating.md +127 -0
- package/docs/page-layout.md +361 -0
- package/docs/permissions.md +260 -0
- package/docs/reports.md +197 -0
- package/docs/state-machines.md +544 -0
- package/docs/tasks/create-object.md +81 -0
- package/docs/translations.md +346 -0
- package/docs/twig-helpers.md +283 -0
- package/docs/webservices.md +159 -0
- package/docs/workspaces.md +176 -0
- package/package.json +59 -0
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { render, Box, Text } from 'ink';
|
|
3
|
+
import Spinner from 'ink-spinner';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
import { loadAuthConfig } from '../utils/auth.js';
|
|
6
|
+
import { ButterflyAPI } from '../utils/api.js';
|
|
7
|
+
import { UploadHandler } from '../utils/uploadHandler.js';
|
|
8
|
+
import { saveObjectWithSpecs, saveReportData } from '../utils/files.js';
|
|
9
|
+
import { DiffEngine } from '../diff/engine.js';
|
|
10
|
+
import { DiffView } from '../components/DiffView.js';
|
|
11
|
+
import { ConflictResolver } from '../components/ConflictResolver.js';
|
|
12
|
+
const DiffCommand = ({ options, api: apiProp }) => {
|
|
13
|
+
const [state, setState] = useState({
|
|
14
|
+
status: 'loading',
|
|
15
|
+
message: 'Loading configuration...',
|
|
16
|
+
diffs: [],
|
|
17
|
+
currentIndex: 0,
|
|
18
|
+
applied: { kept: 0, taken: 0, skipped: 0 }
|
|
19
|
+
});
|
|
20
|
+
const api = apiProp;
|
|
21
|
+
const [engine] = useState(() => new DiffEngine());
|
|
22
|
+
const [remoteData, setRemoteData] = useState(new Map());
|
|
23
|
+
const [showDetails, setShowDetails] = useState(false);
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
const run = async () => {
|
|
26
|
+
try {
|
|
27
|
+
const basePath = options.output || './butterfly-resources';
|
|
28
|
+
setState(s => ({ ...s, status: 'comparing', message: 'Comparing resources...' }));
|
|
29
|
+
const diffs = await engine.diff(apiProp, basePath, options, {
|
|
30
|
+
onLoadingLocal: (type) => setState(s => ({
|
|
31
|
+
...s,
|
|
32
|
+
status: 'fetching_local',
|
|
33
|
+
message: `Loading local ${type}...`
|
|
34
|
+
})),
|
|
35
|
+
onFetchingRemote: (type) => setState(s => ({
|
|
36
|
+
...s,
|
|
37
|
+
status: 'fetching_remote',
|
|
38
|
+
message: `Fetching remote ${type}...`
|
|
39
|
+
})),
|
|
40
|
+
onComparing: (type) => setState(s => ({
|
|
41
|
+
...s,
|
|
42
|
+
status: 'comparing',
|
|
43
|
+
message: `Comparing ${type}...`
|
|
44
|
+
}))
|
|
45
|
+
});
|
|
46
|
+
const adapters = engine.getAdapters();
|
|
47
|
+
const remoteDataMap = new Map();
|
|
48
|
+
for (const [type, adapter] of adapters) {
|
|
49
|
+
if (options.type && type !== options.type)
|
|
50
|
+
continue;
|
|
51
|
+
const remote = await adapter.fetchRemote(apiProp);
|
|
52
|
+
remoteDataMap.set(type, remote);
|
|
53
|
+
}
|
|
54
|
+
setRemoteData(remoteDataMap);
|
|
55
|
+
if (diffs.length === 0) {
|
|
56
|
+
setState(s => ({
|
|
57
|
+
...s,
|
|
58
|
+
status: 'no_changes',
|
|
59
|
+
message: 'No differences found!'
|
|
60
|
+
}));
|
|
61
|
+
setTimeout(() => process.exit(0), 2000);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
setState(s => ({
|
|
65
|
+
...s,
|
|
66
|
+
status: 'reviewing',
|
|
67
|
+
message: '',
|
|
68
|
+
diffs
|
|
69
|
+
}));
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
setState(s => ({
|
|
73
|
+
...s,
|
|
74
|
+
status: 'error',
|
|
75
|
+
message: error instanceof Error ? error.message : 'Unknown error'
|
|
76
|
+
}));
|
|
77
|
+
setTimeout(() => process.exit(1), 3000);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
run();
|
|
81
|
+
}, []);
|
|
82
|
+
const handleResolve = async (action) => {
|
|
83
|
+
const currentDiff = state.diffs[state.currentIndex];
|
|
84
|
+
const newApplied = { ...state.applied };
|
|
85
|
+
const basePath = options.output || './butterfly-resources';
|
|
86
|
+
if (action === 'keep_local' && api && currentDiff.localPath) {
|
|
87
|
+
setState(s => ({ ...s, status: 'applying' }));
|
|
88
|
+
try {
|
|
89
|
+
const handler = new UploadHandler(api);
|
|
90
|
+
if (currentDiff.resourceType === 'objects') {
|
|
91
|
+
await handler.uploadFile(join(currentDiff.localPath, 'object.json'));
|
|
92
|
+
for (const childDiff of currentDiff.children || []) {
|
|
93
|
+
if (childDiff.status !== 'remote_only' && childDiff.resourceName) {
|
|
94
|
+
const specPath = join(currentDiff.localPath, childDiff.resourceName, 'spec.json');
|
|
95
|
+
try {
|
|
96
|
+
await handler.uploadFile(specPath);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
else if (currentDiff.resourceType === 'reports') {
|
|
104
|
+
await handler.uploadFile(join(currentDiff.localPath, 'report.json'));
|
|
105
|
+
for (const childDiff of currentDiff.children || []) {
|
|
106
|
+
if (childDiff.status !== 'remote_only' && childDiff.resourceName) {
|
|
107
|
+
if (childDiff.resourceType === 'cms_report_queries') {
|
|
108
|
+
const queryPath = join(currentDiff.localPath, 'queries', childDiff.resourceName, 'query.json');
|
|
109
|
+
try {
|
|
110
|
+
await handler.uploadFile(queryPath);
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
else if (childDiff.resourceType === 'cms_report_specs') {
|
|
116
|
+
const specPath = join(currentDiff.localPath, 'specs', childDiff.resourceName, 'spec.json');
|
|
117
|
+
try {
|
|
118
|
+
await handler.uploadFile(specPath);
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
newApplied.kept++;
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
console.error('Error uploading:', error);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
else if (action === 'take_remote' && api) {
|
|
133
|
+
setState(s => ({ ...s, status: 'applying' }));
|
|
134
|
+
try {
|
|
135
|
+
const typeRemoteData = remoteData.get(currentDiff.resourceType);
|
|
136
|
+
const remoteResource = typeRemoteData?.get(currentDiff.resourceId);
|
|
137
|
+
if (remoteResource) {
|
|
138
|
+
if (currentDiff.resourceType === 'objects') {
|
|
139
|
+
await saveObjectWithSpecs(basePath, remoteResource.main.table_name, remoteResource.main, remoteResource.children?.specs || []);
|
|
140
|
+
}
|
|
141
|
+
else if (currentDiff.resourceType === 'reports') {
|
|
142
|
+
await saveReportData(basePath, remoteResource.main, remoteResource.children?.specs || [], remoteResource.children?.queries || [], []);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
newApplied.taken++;
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
console.error('Error downloading:', error);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
newApplied.skipped++;
|
|
153
|
+
}
|
|
154
|
+
const nextIndex = state.currentIndex + 1;
|
|
155
|
+
setShowDetails(false);
|
|
156
|
+
if (nextIndex < state.diffs.length) {
|
|
157
|
+
setState(s => ({
|
|
158
|
+
...s,
|
|
159
|
+
status: 'reviewing',
|
|
160
|
+
currentIndex: nextIndex,
|
|
161
|
+
applied: newApplied
|
|
162
|
+
}));
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
setState(s => ({
|
|
166
|
+
...s,
|
|
167
|
+
status: 'complete',
|
|
168
|
+
applied: newApplied,
|
|
169
|
+
message: ''
|
|
170
|
+
}));
|
|
171
|
+
setTimeout(() => process.exit(0), 3000);
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
const summary = engine.getSummary(state.diffs);
|
|
175
|
+
return (React.createElement(Box, { flexDirection: "column" },
|
|
176
|
+
React.createElement(Box, { marginBottom: 1 },
|
|
177
|
+
React.createElement(Text, { bold: true, color: "cyan" }, "Butterfly CLI - Diff")),
|
|
178
|
+
(state.status === 'loading' ||
|
|
179
|
+
state.status === 'fetching_local' ||
|
|
180
|
+
state.status === 'fetching_remote' ||
|
|
181
|
+
state.status === 'comparing') && (React.createElement(Box, null,
|
|
182
|
+
React.createElement(Text, { color: "yellow" },
|
|
183
|
+
React.createElement(Spinner, { type: "dots" }),
|
|
184
|
+
" "),
|
|
185
|
+
React.createElement(Text, null, state.message))),
|
|
186
|
+
state.status === 'no_changes' && (React.createElement(Box, null,
|
|
187
|
+
React.createElement(Text, { color: "green" }, "No differences found between local and remote resources."))),
|
|
188
|
+
state.status === 'reviewing' && state.diffs[state.currentIndex] && (React.createElement(Box, { flexDirection: "column" },
|
|
189
|
+
React.createElement(Box, { marginBottom: 1 },
|
|
190
|
+
React.createElement(Text, { color: "gray" },
|
|
191
|
+
"Found ",
|
|
192
|
+
summary.totalResources,
|
|
193
|
+
" differences: ",
|
|
194
|
+
summary.modified,
|
|
195
|
+
" modified, ",
|
|
196
|
+
summary.localOnly,
|
|
197
|
+
" local only, ",
|
|
198
|
+
summary.remoteOnly,
|
|
199
|
+
" remote only")),
|
|
200
|
+
React.createElement(DiffView, { diff: state.diffs[state.currentIndex], currentIndex: state.currentIndex, totalCount: state.diffs.length, showDetails: showDetails }),
|
|
201
|
+
React.createElement(ConflictResolver, { diff: state.diffs[state.currentIndex], onResolve: handleResolve, onShowDetails: () => setShowDetails(true), isApplying: false }))),
|
|
202
|
+
state.status === 'applying' && (React.createElement(Box, { flexDirection: "column" },
|
|
203
|
+
React.createElement(DiffView, { diff: state.diffs[state.currentIndex], currentIndex: state.currentIndex, totalCount: state.diffs.length }),
|
|
204
|
+
React.createElement(Box, { marginTop: 1 },
|
|
205
|
+
React.createElement(Text, { color: "blue" },
|
|
206
|
+
React.createElement(Spinner, { type: "dots" }),
|
|
207
|
+
" "),
|
|
208
|
+
React.createElement(Text, null, "Applying changes...")))),
|
|
209
|
+
state.status === 'complete' && (React.createElement(Box, { flexDirection: "column" },
|
|
210
|
+
React.createElement(Text, { bold: true, color: "green" }, "Diff complete!"),
|
|
211
|
+
React.createElement(Box, { marginTop: 1, flexDirection: "column" },
|
|
212
|
+
React.createElement(Text, null,
|
|
213
|
+
" Kept local (uploaded): ",
|
|
214
|
+
state.applied.kept),
|
|
215
|
+
React.createElement(Text, null,
|
|
216
|
+
" Took remote (downloaded): ",
|
|
217
|
+
state.applied.taken),
|
|
218
|
+
React.createElement(Text, null,
|
|
219
|
+
" Skipped: ",
|
|
220
|
+
state.applied.skipped)))),
|
|
221
|
+
state.status === 'error' && (React.createElement(Box, null,
|
|
222
|
+
React.createElement(Text, { color: "red" },
|
|
223
|
+
"Error: ",
|
|
224
|
+
state.message)))));
|
|
225
|
+
};
|
|
226
|
+
export default async (options) => {
|
|
227
|
+
const config = await loadAuthConfig();
|
|
228
|
+
if (!config) {
|
|
229
|
+
console.error('No authentication configured. Run "butterfly-cli setup" first.');
|
|
230
|
+
process.exit(1);
|
|
231
|
+
}
|
|
232
|
+
const api = new ButterflyAPI(config);
|
|
233
|
+
await api.authenticate();
|
|
234
|
+
if (api.needs2FA)
|
|
235
|
+
await api.complete2FA();
|
|
236
|
+
render(React.createElement(DiffCommand, { options: options, api: api }));
|
|
237
|
+
};
|
|
238
|
+
//# sourceMappingURL=diff.js.map
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { render, Box, Text } from 'ink';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { DownloadProgress } from '../components/DownloadProgress.js';
|
|
5
|
+
import { loadAuthConfig } from '../utils/auth.js';
|
|
6
|
+
import { ButterflyAPI } from '../utils/api.js';
|
|
7
|
+
import { saveResource, saveObjectWithSpecs, saveReportData, saveAiTask, saveTableRecord, saveStateMachineData, saveWorkflowData, cleanupDirectory } from '../utils/files.js';
|
|
8
|
+
import { generateTableSummary } from '../utils/summaryGenerator.js';
|
|
9
|
+
import { LockManager } from '../utils/lockManager.js';
|
|
10
|
+
async function downloadTableWithSpecs(api, outputPath, tableName, specsTableName, parentIdColumn, setProgress, filter) {
|
|
11
|
+
let allRecords;
|
|
12
|
+
if (filter) {
|
|
13
|
+
allRecords = await api.fetchTable(tableName, filter.column, filter.value);
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
allRecords = await api.fetchTable(tableName);
|
|
17
|
+
}
|
|
18
|
+
const allSpecs = await api.fetchTable(specsTableName);
|
|
19
|
+
const specsByParentId = new Map();
|
|
20
|
+
allSpecs.forEach(spec => {
|
|
21
|
+
const parentId = spec[parentIdColumn];
|
|
22
|
+
if (!specsByParentId.has(parentId)) {
|
|
23
|
+
specsByParentId.set(parentId, []);
|
|
24
|
+
}
|
|
25
|
+
specsByParentId.get(parentId).push(spec);
|
|
26
|
+
});
|
|
27
|
+
setProgress({ current: 0, total: allRecords.length });
|
|
28
|
+
const batchSize = 5;
|
|
29
|
+
for (let i = 0; i < allRecords.length; i += batchSize) {
|
|
30
|
+
const batch = allRecords.slice(i, i + batchSize);
|
|
31
|
+
await Promise.all(batch.map(async (record) => {
|
|
32
|
+
const specs = specsByParentId.get(record.id) || [];
|
|
33
|
+
await saveObjectWithSpecs(outputPath, record.table_name || tableName, record, specs);
|
|
34
|
+
}));
|
|
35
|
+
setProgress({ current: Math.min(i + batchSize, allRecords.length), total: allRecords.length });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async function downloadObjects(api, outputPath, setProgress, includeCore = false) {
|
|
39
|
+
await downloadTableWithSpecs(api, outputPath, 'objects', 'object_specs', 'object_id', setProgress, { column: 'is_cms_object', value: '0' });
|
|
40
|
+
if (includeCore) {
|
|
41
|
+
await downloadTableWithSpecs(api, outputPath, 'objects', 'object_specs', 'object_id', setProgress, { column: 'is_cms_object', value: '1' });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async function downloadPages(api, outputPath, setProgress) {
|
|
45
|
+
const pages = await api.fetchTable('pages');
|
|
46
|
+
setProgress({ current: 0, total: pages.length });
|
|
47
|
+
const batchSize = 5;
|
|
48
|
+
for (let i = 0; i < pages.length; i += batchSize) {
|
|
49
|
+
const batch = pages.slice(i, i + batchSize);
|
|
50
|
+
await Promise.all(batch.map(async (page) => {
|
|
51
|
+
await saveTableRecord(outputPath, 'pages', page);
|
|
52
|
+
}));
|
|
53
|
+
setProgress({ current: Math.min(i + batchSize, pages.length), total: pages.length });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
async function downloadReports(api, outputPath, setProgress) {
|
|
57
|
+
const reports = await api.fetchTable('cms_reports');
|
|
58
|
+
const categories = await api.fetchTable('cms_report_categories');
|
|
59
|
+
const allSpecs = await api.fetchTable('cms_report_specs');
|
|
60
|
+
const allQueries = await api.fetchTable('cms_report_queries');
|
|
61
|
+
const specsByReportId = new Map();
|
|
62
|
+
allSpecs.forEach(spec => {
|
|
63
|
+
const reportId = spec.cms_report_id;
|
|
64
|
+
if (!specsByReportId.has(reportId)) {
|
|
65
|
+
specsByReportId.set(reportId, []);
|
|
66
|
+
}
|
|
67
|
+
specsByReportId.get(reportId).push(spec);
|
|
68
|
+
});
|
|
69
|
+
const queriesByReportId = new Map();
|
|
70
|
+
allQueries.forEach(query => {
|
|
71
|
+
const reportId = query.cms_report_id;
|
|
72
|
+
if (!queriesByReportId.has(reportId)) {
|
|
73
|
+
queriesByReportId.set(reportId, []);
|
|
74
|
+
}
|
|
75
|
+
queriesByReportId.get(reportId).push(query);
|
|
76
|
+
});
|
|
77
|
+
setProgress({ current: 0, total: reports.length });
|
|
78
|
+
const batchSize = 5;
|
|
79
|
+
for (let i = 0; i < reports.length; i += batchSize) {
|
|
80
|
+
const batch = reports.slice(i, i + batchSize);
|
|
81
|
+
await Promise.all(batch.map(async (report) => {
|
|
82
|
+
const specs = specsByReportId.get(report.id) || [];
|
|
83
|
+
const queries = queriesByReportId.get(report.id) || [];
|
|
84
|
+
await saveReportData(outputPath, report, specs, queries, categories);
|
|
85
|
+
}));
|
|
86
|
+
setProgress({ current: Math.min(i + batchSize, reports.length), total: reports.length });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
async function downloadAiTasks(api, outputPath, setProgress) {
|
|
90
|
+
const aiTasks = await api.fetchTable('bfy_ai_tasks');
|
|
91
|
+
setProgress({ current: 0, total: aiTasks.length });
|
|
92
|
+
const batchSize = 5;
|
|
93
|
+
for (let i = 0; i < aiTasks.length; i += batchSize) {
|
|
94
|
+
const batch = aiTasks.slice(i, i + batchSize);
|
|
95
|
+
await Promise.all(batch.map(async (task) => {
|
|
96
|
+
await saveAiTask(outputPath, task);
|
|
97
|
+
}));
|
|
98
|
+
setProgress({ current: Math.min(i + batchSize, aiTasks.length), total: aiTasks.length });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
async function downloadStateMachines(api, outputPath, setProgress) {
|
|
102
|
+
const [stateMachines, states, roles, transitions, transitionSpecs, actions, actionSpecs, transitionActions] = await Promise.all([
|
|
103
|
+
api.fetchTable('bfy_state_machines'),
|
|
104
|
+
api.fetchTable('bfy_state_machine_states'),
|
|
105
|
+
api.fetchTable('bfy_state_machine_roles'),
|
|
106
|
+
api.fetchTable('bfy_state_machine_transitions'),
|
|
107
|
+
api.fetchTable('bfy_state_machine_transition_specs'),
|
|
108
|
+
api.fetchTable('bfy_sm_actions'),
|
|
109
|
+
api.fetchTable('bfy_sm_action_specs'),
|
|
110
|
+
api.fetchTable('bfy_sm_transition_actions')
|
|
111
|
+
]);
|
|
112
|
+
setProgress({ current: 0, total: stateMachines.length });
|
|
113
|
+
const batchSize = 3;
|
|
114
|
+
for (let i = 0; i < stateMachines.length; i += batchSize) {
|
|
115
|
+
const batch = stateMachines.slice(i, i + batchSize);
|
|
116
|
+
await Promise.all(batch.map(async (stateMachine) => {
|
|
117
|
+
const relatedStates = states.filter(s => s.bfy_state_machine_id === stateMachine.id);
|
|
118
|
+
const relatedRoles = roles.filter(r => r.bfy_state_machine_id === stateMachine.id);
|
|
119
|
+
const relatedTransitions = transitions.filter(t => t.bfy_state_machine_id === stateMachine.id);
|
|
120
|
+
const relatedActions = actions.filter(a => a.bfy_state_machine_id === stateMachine.id);
|
|
121
|
+
const transitionIds = relatedTransitions.map(t => t.id);
|
|
122
|
+
const actionIds = relatedActions.map(a => a.id);
|
|
123
|
+
const relatedTransitionSpecs = transitionSpecs.filter(spec => transitionIds.includes(spec.bfy_state_machine_transition_id));
|
|
124
|
+
const relatedActionSpecs = actionSpecs.filter(spec => actionIds.includes(spec.bfy_sm_action_id));
|
|
125
|
+
const relatedTransitionActions = transitionActions.filter(ta => transitionIds.includes(ta.bfy_state_machine_transition_id) &&
|
|
126
|
+
actionIds.includes(ta.bfy_sm_action_id));
|
|
127
|
+
await saveStateMachineData(outputPath, stateMachine, relatedStates, relatedRoles, relatedTransitions, relatedTransitionSpecs, relatedActions, relatedActionSpecs, relatedTransitionActions);
|
|
128
|
+
}));
|
|
129
|
+
setProgress({ current: Math.min(i + batchSize, stateMachines.length), total: stateMachines.length });
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
async function downloadCronjobs(api, outputPath, setProgress) {
|
|
133
|
+
const cronjobs = await api.fetchTable('bfy_cronjobs');
|
|
134
|
+
setProgress({ current: 0, total: cronjobs.length });
|
|
135
|
+
const batchSize = 5;
|
|
136
|
+
for (let i = 0; i < cronjobs.length; i += batchSize) {
|
|
137
|
+
const batch = cronjobs.slice(i, i + batchSize);
|
|
138
|
+
await Promise.all(batch.map(async (cronjob) => {
|
|
139
|
+
await saveTableRecord(outputPath, 'bfy_cronjobs', cronjob);
|
|
140
|
+
}));
|
|
141
|
+
setProgress({ current: Math.min(i + batchSize, cronjobs.length), total: cronjobs.length });
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
async function downloadEmailTemplates(api, outputPath, setProgress) {
|
|
145
|
+
const emailTemplates = await api.fetchTable('cms_email_templates');
|
|
146
|
+
setProgress({ current: 0, total: emailTemplates.length });
|
|
147
|
+
const batchSize = 5;
|
|
148
|
+
for (let i = 0; i < emailTemplates.length; i += batchSize) {
|
|
149
|
+
const batch = emailTemplates.slice(i, i + batchSize);
|
|
150
|
+
await Promise.all(batch.map(async (template) => {
|
|
151
|
+
await saveTableRecord(outputPath, 'cms_email_templates', template);
|
|
152
|
+
}));
|
|
153
|
+
setProgress({ current: Math.min(i + batchSize, emailTemplates.length), total: emailTemplates.length });
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
async function downloadPdfTemplates(api, outputPath, setProgress) {
|
|
157
|
+
const pdfTemplates = await api.fetchTable('pdf_templates');
|
|
158
|
+
setProgress({ current: 0, total: pdfTemplates.length });
|
|
159
|
+
const batchSize = 5;
|
|
160
|
+
for (let i = 0; i < pdfTemplates.length; i += batchSize) {
|
|
161
|
+
const batch = pdfTemplates.slice(i, i + batchSize);
|
|
162
|
+
await Promise.all(batch.map(async (template) => {
|
|
163
|
+
await saveTableRecord(outputPath, 'pdf_templates', template);
|
|
164
|
+
}));
|
|
165
|
+
setProgress({ current: Math.min(i + batchSize, pdfTemplates.length), total: pdfTemplates.length });
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
async function downloadEmailLayouts(api, outputPath, setProgress) {
|
|
169
|
+
try {
|
|
170
|
+
const emailLayouts = await api.fetchTable('cms_email_layouts');
|
|
171
|
+
setProgress({ current: 0, total: emailLayouts.length });
|
|
172
|
+
const batchSize = 5;
|
|
173
|
+
for (let i = 0; i < emailLayouts.length; i += batchSize) {
|
|
174
|
+
const batch = emailLayouts.slice(i, i + batchSize);
|
|
175
|
+
await Promise.all(batch.map(async (layout) => {
|
|
176
|
+
await saveTableRecord(outputPath, 'cms_email_layouts', layout);
|
|
177
|
+
}));
|
|
178
|
+
setProgress({ current: Math.min(i + batchSize, emailLayouts.length), total: emailLayouts.length });
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
catch (error) {
|
|
182
|
+
setProgress({ current: 0, total: 0 });
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
async function downloadWebservices(api, outputPath, setProgress) {
|
|
186
|
+
const webservices = await api.fetchTable('webservices');
|
|
187
|
+
setProgress({ current: 0, total: webservices.length });
|
|
188
|
+
const batchSize = 5;
|
|
189
|
+
for (let i = 0; i < webservices.length; i += batchSize) {
|
|
190
|
+
const batch = webservices.slice(i, i + batchSize);
|
|
191
|
+
await Promise.all(batch.map(async (webservice) => {
|
|
192
|
+
await saveTableRecord(outputPath, 'webservices', webservice);
|
|
193
|
+
}));
|
|
194
|
+
setProgress({ current: Math.min(i + batchSize, webservices.length), total: webservices.length });
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
async function downloadWorkflows(api, outputPath, setProgress) {
|
|
198
|
+
try {
|
|
199
|
+
const [workflows, versions, nodes, connections] = await Promise.all([
|
|
200
|
+
api.fetchTable('bfy_workflows'),
|
|
201
|
+
api.fetchTable('bfy_workflow_versions'),
|
|
202
|
+
api.fetchTable('bfy_workflow_nodes'),
|
|
203
|
+
api.fetchTable('bfy_workflow_connections')
|
|
204
|
+
]);
|
|
205
|
+
setProgress({ current: 0, total: workflows.length });
|
|
206
|
+
const batchSize = 3;
|
|
207
|
+
for (let i = 0; i < workflows.length; i += batchSize) {
|
|
208
|
+
const batch = workflows.slice(i, i + batchSize);
|
|
209
|
+
await Promise.all(batch.map(async (workflow) => {
|
|
210
|
+
const relatedVersions = versions.filter(v => v.bfy_workflow_id === workflow.id);
|
|
211
|
+
const versionIds = relatedVersions.map(v => v.id);
|
|
212
|
+
const relatedNodes = nodes.filter(n => versionIds.includes(n.bfy_workflow_version_id));
|
|
213
|
+
const nodeIds = relatedNodes.map(n => n.id);
|
|
214
|
+
const relatedConnections = connections.filter(c => nodeIds.includes(c.source_node_id) || nodeIds.includes(c.target_node_id));
|
|
215
|
+
await saveWorkflowData(outputPath, workflow, relatedVersions, relatedNodes, relatedConnections);
|
|
216
|
+
}));
|
|
217
|
+
setProgress({ current: Math.min(i + batchSize, workflows.length), total: workflows.length });
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
catch (error) {
|
|
221
|
+
setProgress({ current: 0, total: 0 });
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
async function downloadTranslations(api, outputPath, setProgress) {
|
|
225
|
+
const { promises: fs } = await import('fs');
|
|
226
|
+
const yaml = await import('yaml');
|
|
227
|
+
const [languages, sourceTexts, translations] = await Promise.all([
|
|
228
|
+
api.fetchTable('bfy_languages'),
|
|
229
|
+
api.fetchTable('bfy_translation_texts'),
|
|
230
|
+
api.fetchTable('bfy_translations'),
|
|
231
|
+
]);
|
|
232
|
+
const sourceTextMap = new Map();
|
|
233
|
+
sourceTexts.forEach((t) => sourceTextMap.set(t.id, t.source));
|
|
234
|
+
const translationsByLanguage = new Map();
|
|
235
|
+
translations.forEach((t) => {
|
|
236
|
+
if (!translationsByLanguage.has(t.bfy_language_id)) {
|
|
237
|
+
translationsByLanguage.set(t.bfy_language_id, new Map());
|
|
238
|
+
}
|
|
239
|
+
translationsByLanguage.get(t.bfy_language_id).set(t.bfy_translation_text_id, t.text);
|
|
240
|
+
});
|
|
241
|
+
const translationsDir = join(outputPath, 'translations');
|
|
242
|
+
await fs.mkdir(translationsDir, { recursive: true });
|
|
243
|
+
setProgress({ current: 0, total: languages.length });
|
|
244
|
+
for (let i = 0; i < languages.length; i++) {
|
|
245
|
+
const lang = languages[i];
|
|
246
|
+
const langTranslations = translationsByLanguage.get(lang.id) || new Map();
|
|
247
|
+
const translationData = {};
|
|
248
|
+
sourceTexts.forEach((sourceText) => {
|
|
249
|
+
const translated = langTranslations.get(sourceText.id);
|
|
250
|
+
translationData[sourceText.source] = translated || '';
|
|
251
|
+
});
|
|
252
|
+
const yamlContent = [
|
|
253
|
+
`# ${lang.name} (${lang.iso_code})`,
|
|
254
|
+
`# Language ID: ${lang.id}`,
|
|
255
|
+
`# Total strings: ${sourceTexts.length}`,
|
|
256
|
+
`# Translated: ${langTranslations.size}`,
|
|
257
|
+
`# Untranslated: ${sourceTexts.length - langTranslations.size}`,
|
|
258
|
+
'',
|
|
259
|
+
yaml.stringify(translationData, {
|
|
260
|
+
lineWidth: 0,
|
|
261
|
+
defaultStringType: 'QUOTE_DOUBLE',
|
|
262
|
+
sortMapEntries: true,
|
|
263
|
+
}),
|
|
264
|
+
].join('\n');
|
|
265
|
+
const filePath = join(translationsDir, `${lang.iso_code}.yaml`);
|
|
266
|
+
await fs.writeFile(filePath, yamlContent, 'utf-8');
|
|
267
|
+
setProgress({ current: i + 1, total: languages.length });
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
const DownloadCommand = ({ options, api, isCore }) => {
|
|
271
|
+
const [status, setStatus] = useState('loading');
|
|
272
|
+
const [error, setError] = useState('');
|
|
273
|
+
const [progress, setProgress] = useState({ current: 0, total: 0 });
|
|
274
|
+
useEffect(() => {
|
|
275
|
+
const download = async () => {
|
|
276
|
+
try {
|
|
277
|
+
await LockManager.acquireLock('download');
|
|
278
|
+
LockManager.setupCleanup();
|
|
279
|
+
setStatus('downloading');
|
|
280
|
+
let resourceTypes = options.type ? [options.type] : ['objects'];
|
|
281
|
+
if (!options.type) {
|
|
282
|
+
resourceTypes = ['objects', 'reports', 'bfy_ai_tasks', 'bfy_state_machines', 'pages', 'bfy_cronjobs', 'cms_email_templates', 'pdf_templates', 'cms_email_layouts', 'bfy_workflows', 'webservices', 'translations'];
|
|
283
|
+
}
|
|
284
|
+
const outputPath = options.output || './butterfly-resources';
|
|
285
|
+
if (options.cleanup) {
|
|
286
|
+
if (options.type) {
|
|
287
|
+
await cleanupDirectory(join(outputPath, options.type));
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
290
|
+
await cleanupDirectory(outputPath);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
for (const type of resourceTypes) {
|
|
294
|
+
if (type === 'objects') {
|
|
295
|
+
await downloadObjects(api, outputPath, setProgress, isCore);
|
|
296
|
+
}
|
|
297
|
+
else if (type === 'reports') {
|
|
298
|
+
await downloadReports(api, outputPath, setProgress);
|
|
299
|
+
}
|
|
300
|
+
else if (type === 'bfy_ai_tasks') {
|
|
301
|
+
await downloadAiTasks(api, outputPath, setProgress);
|
|
302
|
+
}
|
|
303
|
+
else if (type === 'bfy_state_machines') {
|
|
304
|
+
await downloadStateMachines(api, outputPath, setProgress);
|
|
305
|
+
}
|
|
306
|
+
else if (type === 'pages') {
|
|
307
|
+
await downloadPages(api, outputPath, setProgress);
|
|
308
|
+
}
|
|
309
|
+
else if (type === 'bfy_cronjobs') {
|
|
310
|
+
await downloadCronjobs(api, outputPath, setProgress);
|
|
311
|
+
}
|
|
312
|
+
else if (type === 'cms_email_templates') {
|
|
313
|
+
await downloadEmailTemplates(api, outputPath, setProgress);
|
|
314
|
+
}
|
|
315
|
+
else if (type === 'pdf_templates') {
|
|
316
|
+
await downloadPdfTemplates(api, outputPath, setProgress);
|
|
317
|
+
}
|
|
318
|
+
else if (type === 'cms_email_layouts') {
|
|
319
|
+
await downloadEmailLayouts(api, outputPath, setProgress);
|
|
320
|
+
}
|
|
321
|
+
else if (type === 'bfy_workflows') {
|
|
322
|
+
await downloadWorkflows(api, outputPath, setProgress);
|
|
323
|
+
}
|
|
324
|
+
else if (type === 'webservices') {
|
|
325
|
+
await downloadWebservices(api, outputPath, setProgress);
|
|
326
|
+
}
|
|
327
|
+
else if (type === 'translations') {
|
|
328
|
+
await downloadTranslations(api, outputPath, setProgress);
|
|
329
|
+
}
|
|
330
|
+
else {
|
|
331
|
+
const resources = await api.downloadResource(type, options.name);
|
|
332
|
+
setProgress({ current: 0, total: resources.length });
|
|
333
|
+
for (let i = 0; i < resources.length; i++) {
|
|
334
|
+
const resource = resources[i];
|
|
335
|
+
await saveResource(outputPath, type, resource.name || resource.id || `${type}-${i}`, resource);
|
|
336
|
+
setProgress({ current: i + 1, total: resources.length });
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
if (resourceTypes.includes('objects')) {
|
|
341
|
+
await generateTableSummary(outputPath);
|
|
342
|
+
}
|
|
343
|
+
setStatus('complete');
|
|
344
|
+
await LockManager.releaseLock();
|
|
345
|
+
setTimeout(() => process.exit(0), 2000);
|
|
346
|
+
}
|
|
347
|
+
catch (err) {
|
|
348
|
+
await LockManager.releaseLock();
|
|
349
|
+
setError(err instanceof Error ? err.message : 'Unknown error');
|
|
350
|
+
setStatus('error');
|
|
351
|
+
setTimeout(() => process.exit(1), 3000);
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
download();
|
|
355
|
+
}, []);
|
|
356
|
+
return (React.createElement(Box, { flexDirection: "column" },
|
|
357
|
+
status === 'loading' && React.createElement(Text, { color: "yellow" }, "Loading configuration..."),
|
|
358
|
+
status === 'downloading' && (React.createElement(DownloadProgress, { resourceType: options.type || 'resources', current: progress.current, total: progress.total, status: "downloading" })),
|
|
359
|
+
status === 'complete' && (React.createElement(DownloadProgress, { resourceType: options.type || 'resources', current: progress.total, total: progress.total, status: "complete" })),
|
|
360
|
+
status === 'error' && (React.createElement(DownloadProgress, { resourceType: options.type || 'resources', current: 0, total: 0, status: "error", error: error }))));
|
|
361
|
+
};
|
|
362
|
+
export default async (options) => {
|
|
363
|
+
const config = await loadAuthConfig();
|
|
364
|
+
if (!config) {
|
|
365
|
+
console.error('No authentication configured. Please run "butterfly-cli setup" first.');
|
|
366
|
+
process.exit(1);
|
|
367
|
+
}
|
|
368
|
+
const api = new ButterflyAPI(config);
|
|
369
|
+
await api.authenticate();
|
|
370
|
+
if (api.needs2FA)
|
|
371
|
+
await api.complete2FA();
|
|
372
|
+
render(React.createElement(DownloadCommand, { options: options, api: api, isCore: config.core || false }));
|
|
373
|
+
};
|
|
374
|
+
//# sourceMappingURL=download.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ButterflyAPI } from '../utils/api.js';
|
|
2
|
+
interface LayoutCommandProps {
|
|
3
|
+
api: ButterflyAPI;
|
|
4
|
+
options: {
|
|
5
|
+
object?: string;
|
|
6
|
+
file?: string;
|
|
7
|
+
data?: string;
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export default function layoutCommand(options: LayoutCommandProps['options']): Promise<void>;
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=layout.d.ts.map
|