@velaro/cli 1.2.0 → 1.4.9
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 +161 -138
- package/bin/velaro.js +177 -62
- package/lib/api.js +91 -52
- package/lib/api.test.js +46 -0
- package/lib/banner.js +76 -0
- package/lib/commands/activity.js +133 -0
- package/lib/commands/acuity.js +66 -0
- package/lib/commands/agent.js +204 -50
- package/lib/commands/ai-config.js +193 -0
- package/lib/commands/ai-models.js +159 -0
- package/lib/commands/appointments.js +198 -0
- package/lib/commands/article.js +668 -388
- package/lib/commands/automation-draft.js +134 -0
- package/lib/commands/avatar.js +75 -0
- package/lib/commands/bigcommerce.js +50 -0
- package/lib/commands/billing-contacts.js +62 -0
- package/lib/commands/billing-email-preference.js +64 -0
- package/lib/commands/billing-subscription.js +265 -0
- package/lib/commands/billing.js +138 -0
- package/lib/commands/bot.js +141 -137
- package/lib/commands/bundle.js +168 -0
- package/lib/commands/calendly.js +62 -0
- package/lib/commands/callback.js +125 -0
- package/lib/commands/callrail.js +88 -0
- package/lib/commands/campaigns.js +44 -0
- package/lib/commands/case.js +102 -0
- package/lib/commands/check.js +163 -163
- package/lib/commands/compliance.js +229 -0
- package/lib/commands/conversation-efficiency.js +178 -0
- package/lib/commands/copilotstudio.js +114 -0
- package/lib/commands/coupon-grant.js +192 -0
- package/lib/commands/db.js +101 -0
- package/lib/commands/deployment.js +107 -107
- package/lib/commands/diagnostics.js +298 -0
- package/lib/commands/email-campaign.js +47 -0
- package/lib/commands/email-inbox.js +88 -0
- package/lib/commands/entitlement.js +176 -0
- package/lib/commands/env.js +45 -45
- package/lib/commands/feature-discovery.js +40 -0
- package/lib/commands/focus.js +278 -0
- package/lib/commands/index.js +38 -5
- package/lib/commands/ingest.js +31 -31
- package/lib/commands/inline-widget-config.js +126 -0
- package/lib/commands/integration.js +93 -0
- package/lib/commands/kb.js +450 -309
- package/lib/commands/login.js +86 -86
- package/lib/commands/logs.js +680 -0
- package/lib/commands/magento.js +210 -0
- package/lib/commands/mcp-key.js +188 -159
- package/lib/commands/migrate.js +134 -0
- package/lib/commands/migration-status.js +66 -0
- package/lib/commands/monday.js +137 -0
- package/lib/commands/netsuite.js +87 -0
- package/lib/commands/notifications.js +63 -0
- package/lib/commands/notion.js +70 -0
- package/lib/commands/ops.js +267 -173
- package/lib/commands/payment-recovery.js +170 -0
- package/lib/commands/pickup.js +172 -0
- package/lib/commands/pricing.js +132 -0
- package/lib/commands/product.js +55 -0
- package/lib/commands/recruiting.js +374 -0
- package/lib/commands/report.js +462 -0
- package/lib/commands/routing.js +304 -0
- package/lib/commands/rule.js +85 -85
- package/lib/commands/sharepoint.js +167 -0
- package/lib/commands/site-provision.js +68 -0
- package/lib/commands/site.js +62 -62
- package/lib/commands/sitesync.js +158 -0
- package/lib/commands/slack.js +64 -0
- package/lib/commands/squarespace.js +108 -0
- package/lib/commands/status.js +24 -24
- package/lib/commands/subscription.js +43 -0
- package/lib/commands/support.js +128 -0
- package/lib/commands/survey.js +216 -0
- package/lib/commands/team.js +144 -144
- package/lib/commands/teams-phone.js +131 -0
- package/lib/commands/teams.js +106 -0
- package/lib/commands/telephony.js +99 -0
- package/lib/commands/update.js +47 -47
- package/lib/commands/webflow.js +128 -0
- package/lib/commands/whoami.js +25 -22
- package/lib/commands/widget-container.js +152 -0
- package/lib/commands/woocommerce.js +240 -0
- package/lib/commands/workflow.js +233 -98
- package/lib/config.js +85 -83
- package/lib/kb-screenshot.js +320 -0
- package/lib/migrations/amscro.json +72 -0
- package/lib/migrations/azenta.json +68 -0
- package/lib/migrations/bluefire.json +49 -0
- package/lib/migrations/donaldson.json +75 -0
- package/lib/oauth.js +149 -135
- package/lib/run.js +21 -16
- package/lib/sharepoint-auth.js +138 -0
- package/lib/subscription.js +41 -39
- package/lib/track.js +35 -35
- package/lib/update-check.js +64 -64
- package/package.json +34 -19
- package/scripts/postinstall.js +12 -0
package/lib/commands/workflow.js
CHANGED
|
@@ -1,98 +1,233 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
.command(
|
|
12
|
-
.command(
|
|
13
|
-
.command(
|
|
14
|
-
.
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
console.
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { get, post, getCredentials, messagingGet } from '../api.js';
|
|
3
|
+
import { requireFeature } from '../subscription.js';
|
|
4
|
+
import { runCommand } from '../run.js';
|
|
5
|
+
|
|
6
|
+
export const workflowCommand = {
|
|
7
|
+
command: 'workflow <subcommand>',
|
|
8
|
+
describe: 'Manage automation workflows',
|
|
9
|
+
builder: (yargs) =>
|
|
10
|
+
yargs
|
|
11
|
+
.command(workflowListCommand)
|
|
12
|
+
.command(workflowGetCommand)
|
|
13
|
+
.command(workflowCreateCommand)
|
|
14
|
+
.command(workflowEnableCommand)
|
|
15
|
+
.command(workflowDisableCommand)
|
|
16
|
+
.command(workflowNodeOptionsCommand)
|
|
17
|
+
.command(workflowIntegrityScanCommand)
|
|
18
|
+
.demandCommand(1, 'Specify a subcommand: list, get, create, enable, disable, node-options, integrity-scan'),
|
|
19
|
+
handler: () => {},
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// ── list ───────────────────────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
const workflowListCommand = {
|
|
25
|
+
command: 'list',
|
|
26
|
+
describe: 'List all workflows',
|
|
27
|
+
handler: runCommand(async () => {
|
|
28
|
+
await requireFeature('enableAutomation', 'Workflows');
|
|
29
|
+
|
|
30
|
+
const workflows = await get('/Workflows/List');
|
|
31
|
+
const rows = (workflows ?? []).filter(w => !w.isTemplate);
|
|
32
|
+
|
|
33
|
+
if (!rows.length) { console.log('No workflows found.'); return; }
|
|
34
|
+
|
|
35
|
+
const idW = Math.max(...rows.map(w => String(w.id).length + 2));
|
|
36
|
+
const triggerW = Math.max('trigger'.length, ...rows.map(w => (w.triggerType ?? '—').length));
|
|
37
|
+
|
|
38
|
+
console.log(`\n${'id'.padStart(idW)} status ${'trigger'.padEnd(triggerW)} name`);
|
|
39
|
+
console.log(`${'-'.repeat(idW)} -------- ${'-'.repeat(triggerW)} ----`);
|
|
40
|
+
for (const w of rows) {
|
|
41
|
+
const id = `[${w.id}]`.padStart(idW);
|
|
42
|
+
const status = w.enabled ? 'enabled ' : 'disabled';
|
|
43
|
+
const trigger = (w.triggerType ?? '—').padEnd(triggerW);
|
|
44
|
+
console.log(`${id} ${status} ${trigger} ${w.name}`);
|
|
45
|
+
}
|
|
46
|
+
console.log(`\n${rows.length} workflow(s)`);
|
|
47
|
+
}),
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// ── get ────────────────────────────────────────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
const workflowGetCommand = {
|
|
53
|
+
command: 'get <id>',
|
|
54
|
+
describe: 'Show workflow details',
|
|
55
|
+
builder: (y) => y.positional('id', { type: 'number', describe: 'Workflow ID' }),
|
|
56
|
+
handler: runCommand(async (argv) => {
|
|
57
|
+
await requireFeature('enableAutomation', 'Workflows');
|
|
58
|
+
|
|
59
|
+
const w = await get(`/Workflows/${argv.id}`);
|
|
60
|
+
if (!w) { console.error(`Workflow ${argv.id} not found.`); process.exit(1); }
|
|
61
|
+
|
|
62
|
+
console.log(`\n[${w.id}] ${w.name}`);
|
|
63
|
+
console.log(` Status: ${w.enabled ? 'enabled' : 'disabled'}`);
|
|
64
|
+
console.log(` Trigger: ${w.triggerType ?? '—'}`);
|
|
65
|
+
if (w.nodes?.length) {
|
|
66
|
+
console.log(` Nodes: ${w.nodes.length}`);
|
|
67
|
+
}
|
|
68
|
+
}),
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// ── create ────────────────────────────────────────────────────────────────────
|
|
72
|
+
|
|
73
|
+
const workflowCreateCommand = {
|
|
74
|
+
command: 'create',
|
|
75
|
+
describe: 'Create a new workflow. Provide --name + --trigger, or --file path/to/workflow.json',
|
|
76
|
+
builder: (y) =>
|
|
77
|
+
y
|
|
78
|
+
.option('name', { type: 'string', describe: 'Workflow name (e.g. "Apple Messages — Inbound")' })
|
|
79
|
+
.option('trigger', { type: 'string', describe: 'Trigger type (e.g. NewConversation, NewMessage, Scheduled)' })
|
|
80
|
+
.option('template', { type: 'number', describe: 'Template workflow ID to copy from' })
|
|
81
|
+
.option('file', { type: 'string', describe: 'JSON file containing NewWorkflowForm body (overrides --name/--trigger)' })
|
|
82
|
+
.option('is-template', { type: 'boolean', default: false, describe: 'Create as reusable template' }),
|
|
83
|
+
handler: runCommand(async (argv) => {
|
|
84
|
+
await requireFeature('enableAutomation', 'Workflows');
|
|
85
|
+
|
|
86
|
+
let body;
|
|
87
|
+
if (argv.file) {
|
|
88
|
+
const raw = fs.readFileSync(argv.file, 'utf8');
|
|
89
|
+
body = JSON.parse(raw);
|
|
90
|
+
} else {
|
|
91
|
+
if (!argv.trigger && !argv.template) {
|
|
92
|
+
console.error('Either --trigger or --template must be provided (or use --file).');
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
body = {
|
|
96
|
+
name: argv.name ?? 'New Workflow',
|
|
97
|
+
trigger: argv.trigger ?? null,
|
|
98
|
+
templateId: argv.template ?? null,
|
|
99
|
+
isTemplate: argv['is-template'] ?? false,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const id = await post('/Workflows', body);
|
|
104
|
+
console.log(`Workflow created: ${id}`);
|
|
105
|
+
console.log(`View: velaro workflow get ${id}`);
|
|
106
|
+
}),
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// ── enable / disable ──────────────────────────────────────────────────────────
|
|
110
|
+
|
|
111
|
+
const workflowEnableCommand = {
|
|
112
|
+
command: 'enable <id>',
|
|
113
|
+
describe: 'Enable a workflow',
|
|
114
|
+
builder: (y) => y.positional('id', { type: 'number', describe: 'Workflow ID' }),
|
|
115
|
+
handler: runCommand(async (argv) => {
|
|
116
|
+
await requireFeature('enableAutomation', 'Workflows');
|
|
117
|
+
await post(`/Workflows/toggle/${argv.id}`, null);
|
|
118
|
+
console.log(`Workflow ${argv.id} enabled.`);
|
|
119
|
+
}),
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const workflowDisableCommand = {
|
|
123
|
+
command: 'disable <id>',
|
|
124
|
+
describe: 'Disable a workflow',
|
|
125
|
+
builder: (y) => y.positional('id', { type: 'number', describe: 'Workflow ID' }),
|
|
126
|
+
handler: runCommand(async (argv) => {
|
|
127
|
+
await requireFeature('enableAutomation', 'Workflows');
|
|
128
|
+
|
|
129
|
+
const w = await get(`/Workflows/${argv.id}`);
|
|
130
|
+
if (!w) { console.error(`Workflow ${argv.id} not found.`); process.exit(1); }
|
|
131
|
+
|
|
132
|
+
if (!w.enabled) {
|
|
133
|
+
console.log(`Workflow ${argv.id} is already disabled.`);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
await post(`/Workflows/toggle/${argv.id}`, null);
|
|
138
|
+
console.log(`Workflow ${argv.id} disabled.`);
|
|
139
|
+
}),
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
// ── node-options ───────────────────────────────────────────────────────────────
|
|
143
|
+
|
|
144
|
+
const workflowNodeOptionsCommand = {
|
|
145
|
+
command: 'node-options <workflowId> <nodeId>',
|
|
146
|
+
describe: 'View or set options on a multiSelect ask-question node',
|
|
147
|
+
builder: (y) =>
|
|
148
|
+
y
|
|
149
|
+
.positional('workflowId', { type: 'number', describe: 'Workflow ID' })
|
|
150
|
+
.positional('nodeId', { type: 'number', describe: 'Node ID' })
|
|
151
|
+
.option('options', { type: 'string', describe: 'JSON array of options: \'[{"label":"A","value":"a"}]\'' })
|
|
152
|
+
.option('min', { type: 'number', describe: 'Minimum number of selections required' })
|
|
153
|
+
.option('max', { type: 'number', describe: 'Maximum number of selections allowed (omit = no limit)' })
|
|
154
|
+
.option('dynamic-var', { type: 'string', describe: 'Variable to load options from at runtime, e.g. <<myList>>' })
|
|
155
|
+
.option('strategy', { type: 'string', choices: ['both', 'paginate', 'cross-channel'], describe: 'Large list overflow strategy' }),
|
|
156
|
+
handler: runCommand(async (argv) => {
|
|
157
|
+
await requireFeature('enableAutomation', 'Workflows');
|
|
158
|
+
|
|
159
|
+
const w = await get(`/Workflows/${argv.workflowId}`);
|
|
160
|
+
if (!w) { console.error(`Workflow ${argv.workflowId} not found.`); process.exit(1); }
|
|
161
|
+
|
|
162
|
+
const node = (w.nodes ?? []).find(n => n.id === argv.nodeId) ?? Object.values(w.nodes ?? {})[argv.nodeId];
|
|
163
|
+
if (!node) { console.error(`Node ${argv.nodeId} not found in workflow ${argv.workflowId}.`); process.exit(1); }
|
|
164
|
+
if (node.actionType !== 'askQuestion' || node.questionType !== 'multiSelect') {
|
|
165
|
+
console.error(`Node ${argv.nodeId} is not a multiSelect ask-question node (found: ${node.actionType}/${node.questionType}).`);
|
|
166
|
+
process.exit(1);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const hasChanges = argv.options || argv.min != null || argv.max != null || argv['dynamic-var'] || argv.strategy;
|
|
170
|
+
|
|
171
|
+
if (!hasChanges) {
|
|
172
|
+
// Read mode — print current config
|
|
173
|
+
console.log(`\nNode ${argv.nodeId} — multiSelect options:`);
|
|
174
|
+
const opts = Array.isArray(node.options) ? node.options : [];
|
|
175
|
+
if (opts.length) {
|
|
176
|
+
console.log(`\n Static options (${opts.length}):`);
|
|
177
|
+
opts.forEach((o, i) => console.log(` ${i + 1}. ${o.label} → "${o.value}"`));
|
|
178
|
+
}
|
|
179
|
+
console.log(`\n Options source: ${node.optionsSource ?? 'static'}`);
|
|
180
|
+
if (node.dynamicOptionsVariable) console.log(` Dynamic variable: ${node.dynamicOptionsVariable}`);
|
|
181
|
+
console.log(` Min selections: ${node.minSelections ?? '(none)'}`);
|
|
182
|
+
console.log(` Max selections: ${node.maxSelections ?? '(no limit)'}`);
|
|
183
|
+
console.log(` Large list: ${node.largeListStrategy ?? 'both (auto)'}`);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Update mode
|
|
188
|
+
const updated = { ...node };
|
|
189
|
+
if (argv.options) {
|
|
190
|
+
updated.options = JSON.parse(argv.options);
|
|
191
|
+
updated.optionsSource = 'static';
|
|
192
|
+
}
|
|
193
|
+
if (argv['dynamic-var']) {
|
|
194
|
+
updated.dynamicOptionsVariable = argv['dynamic-var'];
|
|
195
|
+
updated.optionsSource = 'dynamic';
|
|
196
|
+
}
|
|
197
|
+
if (argv.min != null) updated.minSelections = argv.min;
|
|
198
|
+
if (argv.max != null) updated.maxSelections = argv.max;
|
|
199
|
+
if (argv.strategy) updated.largeListStrategy = argv.strategy;
|
|
200
|
+
|
|
201
|
+
await post(`/Workflows/${argv.workflowId}/node/${argv.nodeId}`, updated);
|
|
202
|
+
console.log(`Node ${argv.nodeId} updated.`);
|
|
203
|
+
}),
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
// ── integrity-scan (Velaro staff only) ──────────────────────────────────────────
|
|
207
|
+
|
|
208
|
+
const workflowIntegrityScanCommand = {
|
|
209
|
+
command: 'integrity-scan <siteId>',
|
|
210
|
+
describe: 'Scan every workflow on a site for NodesBlob that fails to parse (Velaro staff only)',
|
|
211
|
+
builder: (y) => y.positional('siteId', { type: 'number', describe: 'Site ID to scan' }),
|
|
212
|
+
handler: runCommand(async (argv) => {
|
|
213
|
+
const creds = await getCredentials();
|
|
214
|
+
if (creds.siteId !== 1032) {
|
|
215
|
+
console.error('velaro workflow integrity-scan is only available to Velaro staff.');
|
|
216
|
+
process.exit(1);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const results = await messagingGet(`/SuperAdmin/workflows/integrity-scan?siteId=${argv.siteId}`);
|
|
220
|
+
|
|
221
|
+
if (!results?.length) {
|
|
222
|
+
console.log(`\nSite ${argv.siteId}: no corrupted workflows found.\n`);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
console.log(`\nSite ${argv.siteId}: ${results.length} corrupted workflow(s):\n`);
|
|
227
|
+
for (const r of results) {
|
|
228
|
+
console.log(` [${r.id}] ${r.name}`);
|
|
229
|
+
console.log(` Last modified: ${r.lastModified}`);
|
|
230
|
+
console.log(` Error: ${r.error}\n`);
|
|
231
|
+
}
|
|
232
|
+
}),
|
|
233
|
+
};
|
package/lib/config.js
CHANGED
|
@@ -1,83 +1,85 @@
|
|
|
1
|
-
import { homedir } from 'os';
|
|
2
|
-
import { join } from 'path';
|
|
3
|
-
import { readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
4
|
-
|
|
5
|
-
const CONFIG_DIR = join(homedir(), '.velaro');
|
|
6
|
-
const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
return
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
cfg
|
|
77
|
-
cfg.
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
1
|
+
import { homedir } from 'os';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
4
|
+
|
|
5
|
+
const CONFIG_DIR = join(homedir(), '.velaro');
|
|
6
|
+
const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
|
|
7
|
+
|
|
8
|
+
// Each URL can be overridden per-environment via env var (e.g. for a one-off custom API base
|
|
9
|
+
// without editing this file) — VELARO_ADMIN_API_PROD, VELARO_MESSAGING_API_STAGING, etc.
|
|
10
|
+
export const ENVS = {
|
|
11
|
+
prod: {
|
|
12
|
+
adminApiBase: process.env.VELARO_ADMIN_API_PROD || 'https://api-admin-us-east.velaro.com',
|
|
13
|
+
messagingApiBase: process.env.VELARO_MESSAGING_API_PROD || 'https://api-messaging-us-east.velaro.com',
|
|
14
|
+
},
|
|
15
|
+
staging: {
|
|
16
|
+
adminApiBase: process.env.VELARO_ADMIN_API_STAGING || 'https://velaro-admin-staging.azurewebsites.net',
|
|
17
|
+
messagingApiBase: process.env.VELARO_MESSAGING_API_STAGING || 'https://velaro-messaging-api-staging.azurewebsites.net',
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// Legacy single-entry config — migrate transparently on first read.
|
|
22
|
+
function migrate(raw) {
|
|
23
|
+
if (raw.envs) return raw; // already multi-env
|
|
24
|
+
const apiBase = raw.apiBase || '';
|
|
25
|
+
const env = apiBase.includes('staging') ? 'staging' : 'prod';
|
|
26
|
+
return {
|
|
27
|
+
activeEnv: env,
|
|
28
|
+
envs: {
|
|
29
|
+
[env]: {
|
|
30
|
+
adminApiBase: ENVS[env].adminApiBase,
|
|
31
|
+
messagingApiBase: ENVS[env].messagingApiBase,
|
|
32
|
+
velaroToken: raw.velaroToken,
|
|
33
|
+
velaroExpires: raw.velaroExpires,
|
|
34
|
+
entraRefreshToken: raw.entraRefreshToken,
|
|
35
|
+
siteId: raw.siteId,
|
|
36
|
+
userName: raw.userName,
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
_lastUpdateCheck: raw._lastUpdateCheck,
|
|
40
|
+
_latestVersion: raw._latestVersion,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function readConfig() {
|
|
45
|
+
try {
|
|
46
|
+
const raw = JSON.parse(readFileSync(CONFIG_FILE, 'utf8'));
|
|
47
|
+
return migrate(raw);
|
|
48
|
+
} catch {
|
|
49
|
+
return { activeEnv: 'prod', envs: {} };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function writeConfig(data) {
|
|
54
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
55
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(data, null, 2), { mode: 0o600 });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function getActiveEnv() {
|
|
59
|
+
return readConfig().activeEnv || 'prod';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function setActiveEnv(env) {
|
|
63
|
+
if (!ENVS[env]) throw new Error(`Unknown environment "${env}". Use: prod, staging`);
|
|
64
|
+
const cfg = readConfig();
|
|
65
|
+
cfg.activeEnv = env;
|
|
66
|
+
writeConfig(cfg);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function getEnvCredentials(env) {
|
|
70
|
+
const cfg = readConfig();
|
|
71
|
+
return cfg.envs?.[env] || null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function setEnvCredentials(env, creds) {
|
|
75
|
+
if (!ENVS[env]) throw new Error(`Unknown environment "${env}". Use: prod, staging`);
|
|
76
|
+
const cfg = readConfig();
|
|
77
|
+
cfg.envs = cfg.envs || {};
|
|
78
|
+
cfg.envs[env] = { ...ENVS[env], ...creds };
|
|
79
|
+
cfg.activeEnv = cfg.activeEnv || env;
|
|
80
|
+
writeConfig(cfg);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function clearConfig() {
|
|
84
|
+
writeConfig({ activeEnv: 'prod', envs: {} });
|
|
85
|
+
}
|