owosk 0.1.4 → 0.2.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/dist/index.js +785 -383
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/commands/sync.ts
|
|
7
|
-
import * as
|
|
8
|
-
import
|
|
7
|
+
import * as p2 from "@clack/prompts";
|
|
8
|
+
import pc3 from "picocolors";
|
|
9
9
|
|
|
10
10
|
// src/lib/config.ts
|
|
11
11
|
import { join } from "path";
|
|
@@ -15,7 +15,7 @@ import { writeFile } from "fs/promises";
|
|
|
15
15
|
var GLOBAL_CONFIG_DIR = join(homedir(), ".owostack");
|
|
16
16
|
var GLOBAL_CONFIG_PATH = join(GLOBAL_CONFIG_DIR, "config.json");
|
|
17
17
|
function getApiUrl(configUrl) {
|
|
18
|
-
return process.env.OWOSTACK_API_URL || configUrl || "https://
|
|
18
|
+
return process.env.OWOSTACK_API_URL || configUrl || "https://sandbox.owostack.com";
|
|
19
19
|
}
|
|
20
20
|
function getTestApiUrl(configUrl) {
|
|
21
21
|
return process.env.OWOSTACK_API_TEST_URL || configUrl || "https://sandbox.owostack.com";
|
|
@@ -73,7 +73,7 @@ function resolveConfigPath(configPath) {
|
|
|
73
73
|
async function loadOwostackFromConfig(fullPath) {
|
|
74
74
|
try {
|
|
75
75
|
const configModule = await jiti.import(fullPath);
|
|
76
|
-
const instance = configModule.
|
|
76
|
+
const instance = configModule.owo || configModule.default || configModule;
|
|
77
77
|
if (instance && typeof instance.sync === "function") {
|
|
78
78
|
return instance;
|
|
79
79
|
}
|
|
@@ -132,45 +132,518 @@ async function loadConfigSettings(configPath) {
|
|
|
132
132
|
}
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
+
// src/lib/api.ts
|
|
136
|
+
import pc from "picocolors";
|
|
137
|
+
async function fetchPlans(options) {
|
|
138
|
+
if (!options.apiKey) {
|
|
139
|
+
console.error(
|
|
140
|
+
`
|
|
141
|
+
\u274C Missing API key. Pass --key or set OWOSTACK_SECRET_KEY.
|
|
142
|
+
`
|
|
143
|
+
);
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
const url = new URL(`${options.apiUrl}/plans`);
|
|
147
|
+
if (options.group) url.searchParams.set("group", options.group);
|
|
148
|
+
if (options.interval) url.searchParams.set("interval", options.interval);
|
|
149
|
+
if (options.currency) url.searchParams.set("currency", options.currency);
|
|
150
|
+
if (options.includeInactive) url.searchParams.set("includeInactive", "true");
|
|
151
|
+
try {
|
|
152
|
+
const response = await fetch(url.toString(), {
|
|
153
|
+
method: "GET",
|
|
154
|
+
headers: { Authorization: `Bearer ${options.apiKey}` }
|
|
155
|
+
});
|
|
156
|
+
const data = await response.json();
|
|
157
|
+
if (!response.ok || !data?.success) {
|
|
158
|
+
const message = data?.error || data?.message || "Request failed";
|
|
159
|
+
console.error(`
|
|
160
|
+
\u274C Failed to fetch plans: ${message}
|
|
161
|
+
`);
|
|
162
|
+
process.exit(1);
|
|
163
|
+
}
|
|
164
|
+
return data?.plans || [];
|
|
165
|
+
} catch (error) {
|
|
166
|
+
if (error.name === "TypeError" && error.message.includes("fetch failed")) {
|
|
167
|
+
console.error(
|
|
168
|
+
`
|
|
169
|
+
\u274C Connection failed: Could not reach the API at ${pc.cyan(options.apiUrl)}`
|
|
170
|
+
);
|
|
171
|
+
console.error(
|
|
172
|
+
` Please check your internet connection or ensure the API is running.`
|
|
173
|
+
);
|
|
174
|
+
console.error(
|
|
175
|
+
` You can override the API URL by setting the ${pc.bold("OWOSTACK_API_URL")} environment variable.
|
|
176
|
+
`
|
|
177
|
+
);
|
|
178
|
+
} else {
|
|
179
|
+
console.error(`
|
|
180
|
+
\u274C Unexpected error: ${error.message}
|
|
181
|
+
`);
|
|
182
|
+
}
|
|
183
|
+
process.exit(1);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
async function fetchCreditSystems(apiKey, apiUrl) {
|
|
187
|
+
if (!apiKey) {
|
|
188
|
+
return [];
|
|
189
|
+
}
|
|
190
|
+
try {
|
|
191
|
+
const url = `${apiUrl}/credit-systems`;
|
|
192
|
+
const response = await fetch(url, {
|
|
193
|
+
method: "GET",
|
|
194
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
195
|
+
});
|
|
196
|
+
const data = await response.json();
|
|
197
|
+
if (!response.ok || !data?.success) {
|
|
198
|
+
return [];
|
|
199
|
+
}
|
|
200
|
+
return data?.creditSystems || [];
|
|
201
|
+
} catch {
|
|
202
|
+
return [];
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
async function fetchCreditPacks(apiKey, apiUrl) {
|
|
206
|
+
if (!apiKey) {
|
|
207
|
+
return [];
|
|
208
|
+
}
|
|
209
|
+
try {
|
|
210
|
+
const url = `${apiUrl}/credit-packs`;
|
|
211
|
+
const response = await fetch(url, {
|
|
212
|
+
method: "GET",
|
|
213
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
214
|
+
});
|
|
215
|
+
const data = await response.json();
|
|
216
|
+
if (!response.ok || !data?.success) {
|
|
217
|
+
return [];
|
|
218
|
+
}
|
|
219
|
+
return data?.data || [];
|
|
220
|
+
} catch {
|
|
221
|
+
return [];
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// src/lib/diff.ts
|
|
226
|
+
import pc2 from "picocolors";
|
|
227
|
+
import * as p from "@clack/prompts";
|
|
228
|
+
function normalizeFeature(pf) {
|
|
229
|
+
return {
|
|
230
|
+
slug: pf.slug,
|
|
231
|
+
enabled: pf.enabled,
|
|
232
|
+
limit: pf.limit ?? null,
|
|
233
|
+
// Handle both SDK 'reset' and API 'resetInterval'
|
|
234
|
+
reset: pf.reset || pf.resetInterval || "monthly",
|
|
235
|
+
// Handle both SDK 'overage' and API 'overage' (same name)
|
|
236
|
+
overage: pf.overage || "block",
|
|
237
|
+
overagePrice: pf.overagePrice ?? null
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
function normalizePlan(plan) {
|
|
241
|
+
return {
|
|
242
|
+
slug: plan.slug,
|
|
243
|
+
name: plan.name ?? null,
|
|
244
|
+
description: plan.description ?? null,
|
|
245
|
+
price: plan.price ?? 0,
|
|
246
|
+
currency: plan.currency ?? null,
|
|
247
|
+
interval: plan.interval ?? null,
|
|
248
|
+
planGroup: plan.planGroup ?? null,
|
|
249
|
+
trialDays: plan.trialDays ?? 0,
|
|
250
|
+
isAddon: plan.isAddon ?? false,
|
|
251
|
+
autoEnable: plan.autoEnable ?? false,
|
|
252
|
+
features: (plan.features || []).map(normalizeFeature).sort((a, b) => a.slug.localeCompare(b.slug))
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
function normalizeCreditPack(pack) {
|
|
256
|
+
return {
|
|
257
|
+
slug: pack.slug,
|
|
258
|
+
name: pack.name ?? null,
|
|
259
|
+
description: pack.description ?? null,
|
|
260
|
+
credits: pack.credits ?? 0,
|
|
261
|
+
price: pack.price ?? 0,
|
|
262
|
+
currency: pack.currency ?? null,
|
|
263
|
+
creditSystem: pack.creditSystem || pack.creditSystemId || null,
|
|
264
|
+
provider: pack.provider ?? null
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
function diffPlans(localPlans, remotePlans, localCreditSystems = [], remoteCreditSystems = [], localCreditPacks = [], remoteCreditPacks = []) {
|
|
268
|
+
const localMap = /* @__PURE__ */ new Map();
|
|
269
|
+
const remoteMap = /* @__PURE__ */ new Map();
|
|
270
|
+
for (const p9 of localPlans) localMap.set(p9.slug, normalizePlan(p9));
|
|
271
|
+
for (const p9 of remotePlans) remoteMap.set(p9.slug, normalizePlan(p9));
|
|
272
|
+
const onlyLocal = [];
|
|
273
|
+
const onlyRemote = [];
|
|
274
|
+
const changed = [];
|
|
275
|
+
for (const slug of localMap.keys()) {
|
|
276
|
+
if (!remoteMap.has(slug)) onlyLocal.push(slug);
|
|
277
|
+
}
|
|
278
|
+
for (const slug of remoteMap.keys()) {
|
|
279
|
+
if (!localMap.has(slug)) onlyRemote.push(slug);
|
|
280
|
+
}
|
|
281
|
+
for (const slug of localMap.keys()) {
|
|
282
|
+
if (!remoteMap.has(slug)) continue;
|
|
283
|
+
const local = localMap.get(slug);
|
|
284
|
+
const remote = remoteMap.get(slug);
|
|
285
|
+
const details = [];
|
|
286
|
+
const fields = [
|
|
287
|
+
"name",
|
|
288
|
+
"description",
|
|
289
|
+
"price",
|
|
290
|
+
"currency",
|
|
291
|
+
"interval",
|
|
292
|
+
"planGroup",
|
|
293
|
+
"trialDays",
|
|
294
|
+
"isAddon",
|
|
295
|
+
"autoEnable"
|
|
296
|
+
];
|
|
297
|
+
for (const field of fields) {
|
|
298
|
+
if (local[field] !== remote[field]) {
|
|
299
|
+
const localVal = JSON.stringify(local[field]);
|
|
300
|
+
const remoteVal = JSON.stringify(remote[field]);
|
|
301
|
+
details.push(
|
|
302
|
+
`${String(field)}: ${pc2.green(localVal)} \u2192 ${pc2.red(remoteVal)}`
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
const localFeatures = new Map(
|
|
307
|
+
local.features.map((f) => [f.slug, f])
|
|
308
|
+
);
|
|
309
|
+
const remoteFeatures = new Map(
|
|
310
|
+
remote.features.map((f) => [f.slug, f])
|
|
311
|
+
);
|
|
312
|
+
for (const fslug of localFeatures.keys()) {
|
|
313
|
+
if (!remoteFeatures.has(fslug)) {
|
|
314
|
+
details.push(` ${pc2.green("+")} feature ${pc2.bold(fslug)}`);
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
const lf = localFeatures.get(fslug);
|
|
318
|
+
const rf = remoteFeatures.get(fslug);
|
|
319
|
+
if (JSON.stringify(lf) !== JSON.stringify(rf)) {
|
|
320
|
+
details.push(` ${pc2.yellow("~")} feature ${pc2.bold(fslug)}`);
|
|
321
|
+
const featureFields = [
|
|
322
|
+
"enabled",
|
|
323
|
+
"limit",
|
|
324
|
+
"reset",
|
|
325
|
+
"overage",
|
|
326
|
+
"overagePrice"
|
|
327
|
+
];
|
|
328
|
+
for (const ff of featureFields) {
|
|
329
|
+
if (JSON.stringify(lf[ff]) !== JSON.stringify(rf[ff])) {
|
|
330
|
+
const lv = lf[ff] === null ? "unlimited" : String(lf[ff]);
|
|
331
|
+
const rv = rf[ff] === null ? "unlimited" : String(rf[ff]);
|
|
332
|
+
details.push(
|
|
333
|
+
` ${pc2.dim(String(ff))}: ${pc2.green(lv)} \u2192 ${pc2.red(rv)}`
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
for (const fslug of remoteFeatures.keys()) {
|
|
340
|
+
if (!localFeatures.has(fslug)) {
|
|
341
|
+
details.push(` ${pc2.red("-")} feature ${pc2.bold(fslug)}`);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
if (details.length > 0) {
|
|
345
|
+
changed.push({ slug, details });
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
const localCsMap = /* @__PURE__ */ new Map();
|
|
349
|
+
const remoteCsMap = /* @__PURE__ */ new Map();
|
|
350
|
+
for (const cs of localCreditSystems) localCsMap.set(cs.slug, cs);
|
|
351
|
+
for (const cs of remoteCreditSystems) remoteCsMap.set(cs.slug, cs);
|
|
352
|
+
const csOnlyLocal = [];
|
|
353
|
+
const csOnlyRemote = [];
|
|
354
|
+
const csChanged = [];
|
|
355
|
+
for (const slug of localCsMap.keys()) {
|
|
356
|
+
if (!remoteCsMap.has(slug)) csOnlyLocal.push(slug);
|
|
357
|
+
}
|
|
358
|
+
for (const slug of remoteCsMap.keys()) {
|
|
359
|
+
if (!localCsMap.has(slug)) csOnlyRemote.push(slug);
|
|
360
|
+
}
|
|
361
|
+
for (const slug of localCsMap.keys()) {
|
|
362
|
+
if (!remoteCsMap.has(slug)) continue;
|
|
363
|
+
const local = localCsMap.get(slug);
|
|
364
|
+
const remote = remoteCsMap.get(slug);
|
|
365
|
+
const details = [];
|
|
366
|
+
if (local.name !== remote.name) {
|
|
367
|
+
details.push(
|
|
368
|
+
`name: ${pc2.green(JSON.stringify(local.name))} \u2192 ${pc2.red(JSON.stringify(remote.name))}`
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
if (local.description !== remote.description) {
|
|
372
|
+
details.push(
|
|
373
|
+
`description: ${pc2.green(JSON.stringify(local.description))} \u2192 ${pc2.red(JSON.stringify(remote.description))}`
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
const localF = new Map(
|
|
377
|
+
(local.features || []).map((f) => [f.feature, f])
|
|
378
|
+
);
|
|
379
|
+
const remoteF = new Map(
|
|
380
|
+
(remote.features || []).map((f) => [f.feature, f])
|
|
381
|
+
);
|
|
382
|
+
for (const fslug of localF.keys()) {
|
|
383
|
+
if (!remoteF.has(fslug)) {
|
|
384
|
+
details.push(` ${pc2.green("+")} credit cost for ${pc2.bold(fslug)}`);
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
const lf = localF.get(fslug);
|
|
388
|
+
const rf = remoteF.get(fslug);
|
|
389
|
+
if (lf.creditCost !== rf.creditCost) {
|
|
390
|
+
details.push(
|
|
391
|
+
` ${pc2.yellow("~")} credit cost for ${pc2.bold(fslug)}: ${pc2.green(String(lf.creditCost))} \u2192 ${pc2.red(String(rf.creditCost))}`
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
for (const fslug of remoteF.keys()) {
|
|
396
|
+
if (!localF.has(fslug)) {
|
|
397
|
+
details.push(` ${pc2.red("-")} credit cost for ${pc2.bold(fslug)}`);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
if (details.length > 0) {
|
|
401
|
+
csChanged.push({ slug, details });
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
const localPackMap = /* @__PURE__ */ new Map();
|
|
405
|
+
const remotePackMap = /* @__PURE__ */ new Map();
|
|
406
|
+
for (const pack of localCreditPacks)
|
|
407
|
+
localPackMap.set(pack.slug, normalizeCreditPack(pack));
|
|
408
|
+
for (const pack of remoteCreditPacks)
|
|
409
|
+
remotePackMap.set(pack.slug, normalizeCreditPack(pack));
|
|
410
|
+
const packOnlyLocal = [];
|
|
411
|
+
const packOnlyRemote = [];
|
|
412
|
+
const packChanged = [];
|
|
413
|
+
for (const slug of localPackMap.keys()) {
|
|
414
|
+
if (!remotePackMap.has(slug)) packOnlyLocal.push(slug);
|
|
415
|
+
}
|
|
416
|
+
for (const slug of remotePackMap.keys()) {
|
|
417
|
+
if (!localPackMap.has(slug)) packOnlyRemote.push(slug);
|
|
418
|
+
}
|
|
419
|
+
for (const slug of localPackMap.keys()) {
|
|
420
|
+
if (!remotePackMap.has(slug)) continue;
|
|
421
|
+
const local = localPackMap.get(slug);
|
|
422
|
+
const remote = remotePackMap.get(slug);
|
|
423
|
+
const details = [];
|
|
424
|
+
const packFields = [
|
|
425
|
+
"name",
|
|
426
|
+
"description",
|
|
427
|
+
"credits",
|
|
428
|
+
"price",
|
|
429
|
+
"currency",
|
|
430
|
+
"creditSystem",
|
|
431
|
+
"provider"
|
|
432
|
+
];
|
|
433
|
+
for (const field of packFields) {
|
|
434
|
+
if (local[field] !== remote[field]) {
|
|
435
|
+
const localVal = JSON.stringify(local[field]);
|
|
436
|
+
const remoteVal = JSON.stringify(remote[field]);
|
|
437
|
+
details.push(
|
|
438
|
+
`${String(field)}: ${pc2.green(localVal)} \u2192 ${pc2.red(remoteVal)}`
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
if (details.length > 0) {
|
|
443
|
+
packChanged.push({ slug, details });
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
return {
|
|
447
|
+
onlyLocal,
|
|
448
|
+
onlyRemote,
|
|
449
|
+
changed,
|
|
450
|
+
creditSystems: {
|
|
451
|
+
onlyLocal: csOnlyLocal,
|
|
452
|
+
onlyRemote: csOnlyRemote,
|
|
453
|
+
changed: csChanged
|
|
454
|
+
},
|
|
455
|
+
creditPacks: {
|
|
456
|
+
onlyLocal: packOnlyLocal,
|
|
457
|
+
onlyRemote: packOnlyRemote,
|
|
458
|
+
changed: packChanged
|
|
459
|
+
}
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
function printDiff(diff) {
|
|
463
|
+
const hasPlanDiff = diff.onlyLocal.length > 0 || diff.onlyRemote.length > 0 || diff.changed.length > 0;
|
|
464
|
+
const hasCsDiff = diff.creditSystems.onlyLocal.length > 0 || diff.creditSystems.onlyRemote.length > 0 || diff.creditSystems.changed.length > 0;
|
|
465
|
+
const hasPackDiff = diff.creditPacks.onlyLocal.length > 0 || diff.creditPacks.onlyRemote.length > 0 || diff.creditPacks.changed.length > 0;
|
|
466
|
+
if (!hasPlanDiff && !hasCsDiff && !hasPackDiff) {
|
|
467
|
+
p.log.success(pc2.green("Everything is in sync. No differences found."));
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
if (hasPlanDiff) {
|
|
471
|
+
const lines = [];
|
|
472
|
+
if (diff.onlyLocal.length > 0) {
|
|
473
|
+
for (const slug of diff.onlyLocal) {
|
|
474
|
+
lines.push(
|
|
475
|
+
`${pc2.green("+")} ${pc2.bold(slug)} ${pc2.dim("(local only \u2014 will be created on sync)")}`
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
if (diff.onlyRemote.length > 0) {
|
|
480
|
+
for (const slug of diff.onlyRemote) {
|
|
481
|
+
lines.push(
|
|
482
|
+
`${pc2.red("-")} ${pc2.bold(slug)} ${pc2.dim("(remote only \u2014 not in local config)")}`
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
if (diff.changed.length > 0) {
|
|
487
|
+
for (const item of diff.changed) {
|
|
488
|
+
lines.push(`${pc2.yellow("~")} ${pc2.bold(item.slug)}`);
|
|
489
|
+
for (const line of item.details) {
|
|
490
|
+
lines.push(` ${line}`);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
p.note(lines.join("\n"), "Plans Diff");
|
|
495
|
+
}
|
|
496
|
+
if (hasCsDiff) {
|
|
497
|
+
const csLines = [];
|
|
498
|
+
const csDiff = diff.creditSystems;
|
|
499
|
+
if (csDiff.onlyLocal.length > 0) {
|
|
500
|
+
for (const slug of csDiff.onlyLocal) {
|
|
501
|
+
csLines.push(
|
|
502
|
+
`${pc2.green("+")} ${pc2.bold(slug)} ${pc2.dim("(local only \u2014 will be created on sync)")}`
|
|
503
|
+
);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
if (csDiff.onlyRemote.length > 0) {
|
|
507
|
+
for (const slug of csDiff.onlyRemote) {
|
|
508
|
+
csLines.push(
|
|
509
|
+
`${pc2.red("-")} ${pc2.bold(slug)} ${pc2.dim("(remote only \u2014 not in local config)")}`
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
if (csDiff.changed.length > 0) {
|
|
514
|
+
for (const item of csDiff.changed) {
|
|
515
|
+
csLines.push(`${pc2.yellow("~")} ${pc2.bold(item.slug)}`);
|
|
516
|
+
for (const line of item.details) {
|
|
517
|
+
csLines.push(` ${line}`);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
p.note(csLines.join("\n"), "Credit Systems Diff");
|
|
522
|
+
}
|
|
523
|
+
if (hasPackDiff) {
|
|
524
|
+
const packLines = [];
|
|
525
|
+
const packDiff = diff.creditPacks;
|
|
526
|
+
if (packDiff.onlyLocal.length > 0) {
|
|
527
|
+
for (const slug of packDiff.onlyLocal) {
|
|
528
|
+
packLines.push(
|
|
529
|
+
`${pc2.green("+")} ${pc2.bold(slug)} ${pc2.dim("(local only \u2014 will be created on sync)")}`
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
if (packDiff.onlyRemote.length > 0) {
|
|
534
|
+
for (const slug of packDiff.onlyRemote) {
|
|
535
|
+
packLines.push(
|
|
536
|
+
`${pc2.red("-")} ${pc2.bold(slug)} ${pc2.dim("(remote only \u2014 not in local config)")}`
|
|
537
|
+
);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
if (packDiff.changed.length > 0) {
|
|
541
|
+
for (const item of packDiff.changed) {
|
|
542
|
+
packLines.push(`${pc2.yellow("~")} ${pc2.bold(item.slug)}`);
|
|
543
|
+
for (const line of item.details) {
|
|
544
|
+
packLines.push(` ${line}`);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
p.note(packLines.join("\n"), "Credit Packs Diff");
|
|
549
|
+
}
|
|
550
|
+
const planParts = [
|
|
551
|
+
diff.onlyLocal.length > 0 ? `${pc2.green(pc2.bold(diff.onlyLocal.length.toString()))} plans to add` : "",
|
|
552
|
+
diff.onlyRemote.length > 0 ? `${pc2.red(pc2.bold(diff.onlyRemote.length.toString()))} plans to remove` : "",
|
|
553
|
+
diff.changed.length > 0 ? `${pc2.yellow(pc2.bold(diff.changed.length.toString()))} plans modified` : ""
|
|
554
|
+
].filter(Boolean);
|
|
555
|
+
const csParts = [
|
|
556
|
+
diff.creditSystems.onlyLocal.length > 0 ? `${pc2.green(pc2.bold(diff.creditSystems.onlyLocal.length.toString()))} systems to add` : "",
|
|
557
|
+
diff.creditSystems.onlyRemote.length > 0 ? `${pc2.red(pc2.bold(diff.creditSystems.onlyRemote.length.toString()))} systems to remove` : "",
|
|
558
|
+
diff.creditSystems.changed.length > 0 ? `${pc2.yellow(pc2.bold(diff.creditSystems.changed.length.toString()))} systems modified` : ""
|
|
559
|
+
].filter(Boolean);
|
|
560
|
+
const packParts = [
|
|
561
|
+
diff.creditPacks.onlyLocal.length > 0 ? `${pc2.green(pc2.bold(diff.creditPacks.onlyLocal.length.toString()))} packs to add` : "",
|
|
562
|
+
diff.creditPacks.onlyRemote.length > 0 ? `${pc2.red(pc2.bold(diff.creditPacks.onlyRemote.length.toString()))} packs to remove` : "",
|
|
563
|
+
diff.creditPacks.changed.length > 0 ? `${pc2.yellow(pc2.bold(diff.creditPacks.changed.length.toString()))} packs modified` : ""
|
|
564
|
+
].filter(Boolean);
|
|
565
|
+
const parts = [...planParts, ...csParts, ...packParts].join(pc2.dim(" \xB7 "));
|
|
566
|
+
p.log.info(parts);
|
|
567
|
+
}
|
|
568
|
+
|
|
135
569
|
// src/commands/sync.ts
|
|
136
570
|
async function runSyncSingle(options) {
|
|
137
|
-
const { configPath, dryRun,
|
|
571
|
+
const { configPath, dryRun, autoApprove, environment } = options;
|
|
138
572
|
const apiKey = getApiKey(options.apiKey);
|
|
573
|
+
const apiUrl = `${options.apiUrl}/api/v1`;
|
|
139
574
|
const fullPath = resolveConfigPath(configPath);
|
|
140
575
|
if (!fullPath) {
|
|
141
|
-
|
|
142
|
-
|
|
576
|
+
p2.log.error(
|
|
577
|
+
pc3.red(
|
|
143
578
|
`Configuration file not found.${configPath ? ` looked at ${configPath}` : " searched defaults."}`
|
|
144
579
|
)
|
|
145
580
|
);
|
|
146
581
|
process.exit(1);
|
|
147
582
|
}
|
|
148
|
-
const s =
|
|
149
|
-
s.start(`Loading ${
|
|
583
|
+
const s = p2.spinner();
|
|
584
|
+
s.start(`Loading ${pc3.cyan(fullPath)}`);
|
|
150
585
|
let owo;
|
|
151
586
|
try {
|
|
152
587
|
owo = await loadOwostackFromConfig(fullPath);
|
|
153
588
|
} catch (e) {
|
|
154
|
-
s.stop(
|
|
155
|
-
|
|
156
|
-
|
|
589
|
+
s.stop(pc3.red("Failed to load configuration"));
|
|
590
|
+
p2.log.error(pc3.red(`Error: ${e.message}`));
|
|
591
|
+
p2.note(
|
|
157
592
|
`import { Owostack, metered, boolean, plan } from "owostack";
|
|
158
593
|
export default new Owostack({ secretKey: "...", catalog: [...] });`,
|
|
159
594
|
"Example owo.config.ts"
|
|
160
595
|
);
|
|
161
|
-
|
|
162
|
-
|
|
596
|
+
p2.log.info(
|
|
597
|
+
pc3.dim(
|
|
163
598
|
"Make sure 'owostack' is installed in your project: 'npm install owostack'"
|
|
164
599
|
)
|
|
165
600
|
);
|
|
166
601
|
process.exit(1);
|
|
167
602
|
}
|
|
168
603
|
if (!owo || typeof owo.sync !== "function") {
|
|
169
|
-
s.stop(
|
|
170
|
-
|
|
604
|
+
s.stop(pc3.red("Invalid configuration"));
|
|
605
|
+
p2.log.error("Config file must export an Owostack instance.");
|
|
171
606
|
process.exit(1);
|
|
172
607
|
}
|
|
173
|
-
s.
|
|
608
|
+
s.stop("Configuration loaded");
|
|
609
|
+
const { buildSyncPayload } = await import("owostack").catch(() => ({
|
|
610
|
+
buildSyncPayload: null
|
|
611
|
+
}));
|
|
612
|
+
const localPayload = buildSyncPayload?.(owo._config.catalog);
|
|
613
|
+
s.start(`Fetching remote catalog from ${pc3.dim(environment)}...`);
|
|
614
|
+
const remotePlans = await fetchPlans({ apiKey, apiUrl });
|
|
615
|
+
const remoteCreditSystems = await fetchCreditSystems(apiKey, apiUrl);
|
|
616
|
+
const remoteCreditPacks = await fetchCreditPacks(apiKey, apiUrl);
|
|
617
|
+
s.stop("Remote catalog fetched");
|
|
618
|
+
const diff = diffPlans(
|
|
619
|
+
localPayload?.plans ?? [],
|
|
620
|
+
remotePlans,
|
|
621
|
+
localPayload?.creditSystems ?? [],
|
|
622
|
+
remoteCreditSystems,
|
|
623
|
+
localPayload?.creditPacks ?? [],
|
|
624
|
+
remoteCreditPacks
|
|
625
|
+
);
|
|
626
|
+
printDiff(diff);
|
|
627
|
+
const hasChanges = diff.onlyLocal.length > 0 || diff.onlyRemote.length > 0 || diff.changed.length > 0 || diff.creditSystems.onlyLocal.length > 0 || diff.creditSystems.onlyRemote.length > 0 || diff.creditSystems.changed.length > 0 || diff.creditPacks.onlyLocal.length > 0 || diff.creditPacks.onlyRemote.length > 0 || diff.creditPacks.changed.length > 0;
|
|
628
|
+
if (!hasChanges) {
|
|
629
|
+
p2.outro(pc3.green("Everything is already in sync! \u2728"));
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
632
|
+
if (dryRun) {
|
|
633
|
+
p2.log.info(pc3.yellow("Dry run - no changes were applied."));
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
if (!autoApprove) {
|
|
637
|
+
const confirm4 = await p2.confirm({
|
|
638
|
+
message: `Proceed with sync to ${pc3.cyan(environment)}?`,
|
|
639
|
+
initialValue: false
|
|
640
|
+
});
|
|
641
|
+
if (p2.isCancel(confirm4) || !confirm4) {
|
|
642
|
+
p2.outro(pc3.yellow("Sync cancelled"));
|
|
643
|
+
process.exit(0);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
s.start(`Syncing with ${pc3.cyan(environment)}...`);
|
|
174
647
|
if (apiKey && typeof owo.setSecretKey === "function") {
|
|
175
648
|
owo.setSecretKey(apiKey);
|
|
176
649
|
}
|
|
@@ -178,189 +651,130 @@ export default new Owostack({ secretKey: "...", catalog: [...] });`,
|
|
|
178
651
|
owo.setApiUrl(apiUrl);
|
|
179
652
|
}
|
|
180
653
|
try {
|
|
181
|
-
if (dryRun) {
|
|
182
|
-
s.stop(pc.yellow("Dry run mode (showing catalog payload)"));
|
|
183
|
-
const { buildSyncPayload } = await import("owostack").catch(() => {
|
|
184
|
-
return { buildSyncPayload: null };
|
|
185
|
-
});
|
|
186
|
-
if (buildSyncPayload && owo._config?.catalog) {
|
|
187
|
-
const payload = buildSyncPayload(owo._config.catalog);
|
|
188
|
-
let featureSummary = payload.features.map(
|
|
189
|
-
(f) => `${pc.green("+")} ${pc.bold(f.slug)} ${pc.dim(`(${f.type})`)}`
|
|
190
|
-
).join("\n");
|
|
191
|
-
p.note(
|
|
192
|
-
featureSummary || pc.dim("No features defined"),
|
|
193
|
-
"Features to Sync"
|
|
194
|
-
);
|
|
195
|
-
let planSummary = "";
|
|
196
|
-
for (const p_obj of payload.plans) {
|
|
197
|
-
planSummary += `${pc.green("+")} ${pc.bold(p_obj.slug)} ${pc.dim(`${p_obj.currency} ${p_obj.price}/${p_obj.interval}`)}
|
|
198
|
-
`;
|
|
199
|
-
for (const f of p_obj.features) {
|
|
200
|
-
const status = f.enabled ? pc.green("\u2713") : pc.red("\u2717");
|
|
201
|
-
const configParts = [];
|
|
202
|
-
if (f.limit !== void 0)
|
|
203
|
-
configParts.push(
|
|
204
|
-
`limit: ${f.limit === null ? "unlimited" : f.limit}`
|
|
205
|
-
);
|
|
206
|
-
if (f.reset) configParts.push(`reset: ${f.reset}`);
|
|
207
|
-
if (f.overage) configParts.push(`overage: ${f.overage}`);
|
|
208
|
-
if (f.overagePrice) configParts.push(`price: ${f.overagePrice}`);
|
|
209
|
-
const configStr = configParts.length > 0 ? ` ${pc.dim(`(${configParts.join(", ")})`)}` : "";
|
|
210
|
-
planSummary += ` ${status} ${pc.dim(f.slug)}${configStr}
|
|
211
|
-
`;
|
|
212
|
-
}
|
|
213
|
-
planSummary += "\n";
|
|
214
|
-
}
|
|
215
|
-
p.note(
|
|
216
|
-
planSummary.trim() || pc.dim("No plans defined"),
|
|
217
|
-
"Plans to Sync"
|
|
218
|
-
);
|
|
219
|
-
}
|
|
220
|
-
p.log.info(pc.yellow("No changes were applied to the server."));
|
|
221
|
-
return;
|
|
222
|
-
}
|
|
223
654
|
const result = await owo.sync();
|
|
224
|
-
s.stop(
|
|
655
|
+
s.stop(pc3.green("Sync completed"));
|
|
225
656
|
if (!result.success) {
|
|
226
|
-
|
|
657
|
+
p2.log.error(pc3.red("Sync failed"));
|
|
227
658
|
process.exit(1);
|
|
228
659
|
}
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
if (
|
|
241
|
-
|
|
660
|
+
const lines = [];
|
|
661
|
+
if (diff.onlyLocal.length > 0) {
|
|
662
|
+
for (const slug of diff.onlyLocal) {
|
|
663
|
+
lines.push(`${pc3.green("+")} ${pc3.bold(slug)}`);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
if (diff.onlyRemote.length > 0) {
|
|
667
|
+
for (const slug of diff.onlyRemote) {
|
|
668
|
+
lines.push(`${pc3.red("-")} ${pc3.bold(slug)}`);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
if (diff.changed.length > 0) {
|
|
672
|
+
for (const item of diff.changed) {
|
|
673
|
+
lines.push(`${pc3.cyan("~")} ${pc3.bold(item.slug)}`);
|
|
674
|
+
for (const detail of item.details) {
|
|
675
|
+
lines.push(` ${detail}`);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
if (diff.creditSystems.onlyLocal.length > 0) {
|
|
680
|
+
for (const slug of diff.creditSystems.onlyLocal) {
|
|
681
|
+
lines.push(`${pc3.green("+")} ${pc3.bold(slug)}`);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
if (diff.creditSystems.onlyRemote.length > 0) {
|
|
685
|
+
for (const slug of diff.creditSystems.onlyRemote) {
|
|
686
|
+
lines.push(`${pc3.red("-")} ${pc3.bold(slug)}`);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
if (diff.creditSystems.changed.length > 0) {
|
|
690
|
+
for (const item of diff.creditSystems.changed) {
|
|
691
|
+
lines.push(`${pc3.cyan("~")} ${pc3.bold(item.slug)}`);
|
|
692
|
+
for (const detail of item.details) {
|
|
693
|
+
lines.push(` ${detail}`);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
if (diff.creditPacks.onlyLocal.length > 0) {
|
|
698
|
+
for (const slug of diff.creditPacks.onlyLocal) {
|
|
699
|
+
lines.push(`${pc3.green("+")} ${pc3.bold(slug)}`);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
if (diff.creditPacks.onlyRemote.length > 0) {
|
|
703
|
+
for (const slug of diff.creditPacks.onlyRemote) {
|
|
704
|
+
lines.push(`${pc3.red("-")} ${pc3.bold(slug)}`);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
if (diff.creditPacks.changed.length > 0) {
|
|
708
|
+
for (const item of diff.creditPacks.changed) {
|
|
709
|
+
lines.push(`${pc3.cyan("~")} ${pc3.bold(item.slug)}`);
|
|
710
|
+
for (const detail of item.details) {
|
|
711
|
+
lines.push(` ${detail}`);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
if (lines.length > 0) {
|
|
716
|
+
p2.note(lines.join("\n"), "Changes applied");
|
|
717
|
+
const counts = [
|
|
718
|
+
diff.onlyLocal.length > 0 ? `${pc3.green(pc3.bold(diff.onlyLocal.length.toString()))} plans added` : "",
|
|
719
|
+
diff.onlyRemote.length > 0 ? `${pc3.red(pc3.bold(diff.onlyRemote.length.toString()))} plans removed` : "",
|
|
720
|
+
diff.changed.length > 0 ? `${pc3.cyan(pc3.bold(diff.changed.length.toString()))} plans modified` : "",
|
|
721
|
+
diff.creditSystems.onlyLocal.length > 0 ? `${pc3.green(pc3.bold(diff.creditSystems.onlyLocal.length.toString()))} systems added` : "",
|
|
722
|
+
diff.creditSystems.onlyRemote.length > 0 ? `${pc3.red(pc3.bold(diff.creditSystems.onlyRemote.length.toString()))} systems removed` : "",
|
|
723
|
+
diff.creditSystems.changed.length > 0 ? `${pc3.cyan(pc3.bold(diff.creditSystems.changed.length.toString()))} systems modified` : "",
|
|
724
|
+
diff.creditPacks.onlyLocal.length > 0 ? `${pc3.green(pc3.bold(diff.creditPacks.onlyLocal.length.toString()))} packs added` : "",
|
|
725
|
+
diff.creditPacks.onlyRemote.length > 0 ? `${pc3.red(pc3.bold(diff.creditPacks.onlyRemote.length.toString()))} packs removed` : "",
|
|
726
|
+
diff.creditPacks.changed.length > 0 ? `${pc3.cyan(pc3.bold(diff.creditPacks.changed.length.toString()))} packs modified` : ""
|
|
727
|
+
].filter(Boolean).join(pc3.dim(" \xB7 "));
|
|
728
|
+
p2.log.info(counts);
|
|
242
729
|
} else {
|
|
243
|
-
|
|
730
|
+
p2.log.success(pc3.dim("No changes detected. Catalog is up to date."));
|
|
244
731
|
}
|
|
245
|
-
if (result.warnings.length) {
|
|
246
|
-
|
|
732
|
+
if (result.warnings && result.warnings.length) {
|
|
733
|
+
p2.log.warn(pc3.yellow(`Warnings:
|
|
247
734
|
${result.warnings.join("\n")}`));
|
|
248
735
|
}
|
|
249
736
|
} catch (e) {
|
|
250
|
-
s.stop(
|
|
251
|
-
|
|
737
|
+
s.stop(pc3.red("Sync failed"));
|
|
738
|
+
p2.log.error(e.message);
|
|
252
739
|
throw e;
|
|
253
740
|
}
|
|
254
741
|
}
|
|
255
742
|
async function runSync(options) {
|
|
256
|
-
|
|
743
|
+
p2.intro(pc3.bgYellow(pc3.black(" sync ")));
|
|
257
744
|
const configSettings = await loadConfigSettings(options.config);
|
|
258
|
-
let apiUrl = configSettings.apiUrl;
|
|
259
745
|
const testUrl = getTestApiUrl(configSettings.environments?.test);
|
|
260
746
|
const liveUrl = getApiUrl(configSettings.environments?.live);
|
|
261
747
|
if (options.prod) {
|
|
262
|
-
|
|
263
|
-
await runSyncSingle({
|
|
264
|
-
configPath: options.config,
|
|
265
|
-
dryRun: !!options.dryRun,
|
|
266
|
-
apiKey: options.key || "",
|
|
267
|
-
apiUrl: apiUrl || `${testUrl}/api/v1`
|
|
268
|
-
});
|
|
748
|
+
p2.log.step(pc3.magenta("Production Mode: Syncing to PROD environment"));
|
|
269
749
|
await runSyncSingle({
|
|
270
750
|
configPath: options.config,
|
|
271
751
|
dryRun: !!options.dryRun,
|
|
752
|
+
autoApprove: !!options.yes,
|
|
272
753
|
apiKey: options.key || "",
|
|
273
|
-
apiUrl: apiUrl ||
|
|
754
|
+
apiUrl: configSettings.apiUrl || liveUrl,
|
|
755
|
+
environment: "prod"
|
|
274
756
|
});
|
|
275
757
|
} else {
|
|
758
|
+
p2.log.step(pc3.cyan("Sandbox Mode: Syncing to SANDBOX environment"));
|
|
276
759
|
await runSyncSingle({
|
|
277
760
|
configPath: options.config,
|
|
278
761
|
dryRun: !!options.dryRun,
|
|
762
|
+
autoApprove: !!options.yes,
|
|
279
763
|
apiKey: options.key || "",
|
|
280
|
-
apiUrl:
|
|
764
|
+
apiUrl: testUrl,
|
|
765
|
+
environment: "sandbox"
|
|
281
766
|
});
|
|
282
767
|
}
|
|
283
|
-
|
|
768
|
+
p2.outro(pc3.green("Done! \u2728"));
|
|
284
769
|
}
|
|
285
770
|
|
|
286
771
|
// src/commands/pull.ts
|
|
287
|
-
import * as
|
|
288
|
-
import
|
|
772
|
+
import * as p3 from "@clack/prompts";
|
|
773
|
+
import pc4 from "picocolors";
|
|
289
774
|
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
290
775
|
import { writeFile as writeFile2 } from "fs/promises";
|
|
291
776
|
import { join as join2, resolve as resolve2, extname as extname2, isAbsolute as isAbsolute2 } from "path";
|
|
292
777
|
|
|
293
|
-
// src/lib/api.ts
|
|
294
|
-
import pc2 from "picocolors";
|
|
295
|
-
async function fetchPlans(options) {
|
|
296
|
-
if (!options.apiKey) {
|
|
297
|
-
console.error(
|
|
298
|
-
`
|
|
299
|
-
\u274C Missing API key. Pass --key or set OWOSTACK_SECRET_KEY.
|
|
300
|
-
`
|
|
301
|
-
);
|
|
302
|
-
process.exit(1);
|
|
303
|
-
}
|
|
304
|
-
const url = new URL(`${options.apiUrl}/plans`);
|
|
305
|
-
if (options.group) url.searchParams.set("group", options.group);
|
|
306
|
-
if (options.interval) url.searchParams.set("interval", options.interval);
|
|
307
|
-
if (options.currency) url.searchParams.set("currency", options.currency);
|
|
308
|
-
if (options.includeInactive) url.searchParams.set("includeInactive", "true");
|
|
309
|
-
try {
|
|
310
|
-
const response = await fetch(url.toString(), {
|
|
311
|
-
method: "GET",
|
|
312
|
-
headers: { Authorization: `Bearer ${options.apiKey}` }
|
|
313
|
-
});
|
|
314
|
-
const data = await response.json();
|
|
315
|
-
if (!response.ok || !data?.success) {
|
|
316
|
-
const message = data?.error || data?.message || "Request failed";
|
|
317
|
-
console.error(`
|
|
318
|
-
\u274C Failed to fetch plans: ${message}
|
|
319
|
-
`);
|
|
320
|
-
process.exit(1);
|
|
321
|
-
}
|
|
322
|
-
return data?.plans || [];
|
|
323
|
-
} catch (error) {
|
|
324
|
-
if (error.name === "TypeError" && error.message.includes("fetch failed")) {
|
|
325
|
-
console.error(
|
|
326
|
-
`
|
|
327
|
-
\u274C Connection failed: Could not reach the API at ${pc2.cyan(options.apiUrl)}`
|
|
328
|
-
);
|
|
329
|
-
console.error(
|
|
330
|
-
` Please check your internet connection or ensure the API is running.`
|
|
331
|
-
);
|
|
332
|
-
console.error(
|
|
333
|
-
` You can override the API URL by setting the ${pc2.bold("OWOSTACK_API_URL")} environment variable.
|
|
334
|
-
`
|
|
335
|
-
);
|
|
336
|
-
} else {
|
|
337
|
-
console.error(`
|
|
338
|
-
\u274C Unexpected error: ${error.message}
|
|
339
|
-
`);
|
|
340
|
-
}
|
|
341
|
-
process.exit(1);
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
async function fetchCreditSystems(apiKey, apiUrl) {
|
|
345
|
-
if (!apiKey) {
|
|
346
|
-
return [];
|
|
347
|
-
}
|
|
348
|
-
try {
|
|
349
|
-
const url = `${apiUrl}/credit-systems`;
|
|
350
|
-
const response = await fetch(url, {
|
|
351
|
-
method: "GET",
|
|
352
|
-
headers: { Authorization: `Bearer ${apiKey}` }
|
|
353
|
-
});
|
|
354
|
-
const data = await response.json();
|
|
355
|
-
if (!response.ok || !data?.success) {
|
|
356
|
-
return [];
|
|
357
|
-
}
|
|
358
|
-
return data?.creditSystems || [];
|
|
359
|
-
} catch {
|
|
360
|
-
return [];
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
|
|
364
778
|
// src/lib/generate.ts
|
|
365
779
|
function slugToIdentifier(slug, used) {
|
|
366
780
|
const parts = slug.replace(/[^a-zA-Z0-9]+/g, " ").trim().split(/\s+/).filter(Boolean);
|
|
@@ -414,7 +828,7 @@ function slugToIdentifier(slug, used) {
|
|
|
414
828
|
used.add(candidate);
|
|
415
829
|
return candidate;
|
|
416
830
|
}
|
|
417
|
-
function generateConfig(plans, creditSystems = [], defaultProvider, format = "ts") {
|
|
831
|
+
function generateConfig(plans, creditSystems = [], creditPacks = [], defaultProvider, format = "ts") {
|
|
418
832
|
const isTs = format === "ts";
|
|
419
833
|
const isCjs = format === "cjs";
|
|
420
834
|
const creditSystemSlugs = new Set(creditSystems.map((cs) => cs.slug));
|
|
@@ -500,6 +914,8 @@ function generateConfig(plans, creditSystems = [], defaultProvider, format = "ts
|
|
|
500
914
|
configLines.push(`trialDays: ${plan.trialDays}`);
|
|
501
915
|
if (plan.provider)
|
|
502
916
|
configLines.push(`provider: ${JSON.stringify(plan.provider)}`);
|
|
917
|
+
if (plan.autoEnable) configLines.push(`autoEnable: true`);
|
|
918
|
+
if (plan.isAddon) configLines.push(`isAddon: true`);
|
|
503
919
|
const featureEntries = [];
|
|
504
920
|
for (const pf of plan.features || []) {
|
|
505
921
|
if (creditSystemSlugs.has(pf.slug)) {
|
|
@@ -570,7 +986,30 @@ function generateConfig(plans, creditSystems = [], defaultProvider, format = "ts
|
|
|
570
986
|
})`
|
|
571
987
|
);
|
|
572
988
|
}
|
|
989
|
+
const creditPackLines = [];
|
|
990
|
+
for (const pack of creditPacks) {
|
|
991
|
+
const configLines = [];
|
|
992
|
+
configLines.push(`name: ${JSON.stringify(pack.name)}`);
|
|
993
|
+
if (pack.description)
|
|
994
|
+
configLines.push(`description: ${JSON.stringify(pack.description)}`);
|
|
995
|
+
configLines.push(`credits: ${pack.credits}`);
|
|
996
|
+
configLines.push(`price: ${pack.price}`);
|
|
997
|
+
configLines.push(`currency: ${JSON.stringify(pack.currency)}`);
|
|
998
|
+
configLines.push(
|
|
999
|
+
`creditSystem: ${JSON.stringify(pack.creditSystemId || pack.creditSystem)}`
|
|
1000
|
+
);
|
|
1001
|
+
if (pack.provider)
|
|
1002
|
+
configLines.push(`provider: ${JSON.stringify(pack.provider)}`);
|
|
1003
|
+
if (pack.metadata)
|
|
1004
|
+
configLines.push(`metadata: ${JSON.stringify(pack.metadata)}`);
|
|
1005
|
+
creditPackLines.push(
|
|
1006
|
+
`creditPack(${JSON.stringify(pack.slug)}, {
|
|
1007
|
+
${configLines.join(",\n ")}
|
|
1008
|
+
})`
|
|
1009
|
+
);
|
|
1010
|
+
}
|
|
573
1011
|
const hasCreditSystems = creditSystemLines.length > 0;
|
|
1012
|
+
const hasCreditPacks = creditPackLines.length > 0;
|
|
574
1013
|
const hasEntities = Array.from(featuresBySlug.values()).some(
|
|
575
1014
|
(f) => f.meterType === "non_consumable"
|
|
576
1015
|
);
|
|
@@ -578,7 +1017,7 @@ function generateConfig(plans, creditSystems = [], defaultProvider, format = "ts
|
|
|
578
1017
|
` : "";
|
|
579
1018
|
const importParts = ["Owostack", "metered", "boolean"];
|
|
580
1019
|
if (hasEntities) importParts.push("entity");
|
|
581
|
-
importParts.push("creditSystem", "plan");
|
|
1020
|
+
importParts.push("creditSystem", "creditPack", "plan");
|
|
582
1021
|
const imports = isCjs ? `const { ${importParts.join(", ")} } = require("owostack");` : `import { ${importParts.join(", ")} } from "owostack";`;
|
|
583
1022
|
const tsCheck = !isTs ? `// @ts-check` : "";
|
|
584
1023
|
const jsDoc = !isTs ? `/** @type {import('owostack').Owostack} */` : "";
|
|
@@ -590,13 +1029,15 @@ function generateConfig(plans, creditSystems = [], defaultProvider, format = "ts
|
|
|
590
1029
|
``,
|
|
591
1030
|
...featureLines,
|
|
592
1031
|
...hasCreditSystems ? ["", ...creditSystemLines] : [],
|
|
1032
|
+
...hasCreditPacks ? ["", ...creditPackLines] : [],
|
|
593
1033
|
``,
|
|
594
1034
|
jsDoc,
|
|
595
1035
|
`${owoDecl} new Owostack({`,
|
|
596
1036
|
` secretKey: ${secretKey},`,
|
|
597
1037
|
providerLine,
|
|
598
1038
|
` catalog: [`,
|
|
599
|
-
` ${planLines.join(",\n ")}`,
|
|
1039
|
+
` ${planLines.join(",\n ")}${hasCreditPacks ? "," : ""}`,
|
|
1040
|
+
...hasCreditPacks ? [` ${creditPackLines.join(",\n ")}`] : [],
|
|
600
1041
|
` ],`,
|
|
601
1042
|
`});`,
|
|
602
1043
|
``
|
|
@@ -608,8 +1049,8 @@ function getProjectInfo() {
|
|
|
608
1049
|
const cwd = process.cwd();
|
|
609
1050
|
let isEsm = false;
|
|
610
1051
|
try {
|
|
611
|
-
const
|
|
612
|
-
isEsm =
|
|
1052
|
+
const pkg2 = JSON.parse(readFileSync2(join2(cwd, "package.json"), "utf8"));
|
|
1053
|
+
isEsm = pkg2.type === "module";
|
|
613
1054
|
} catch {
|
|
614
1055
|
}
|
|
615
1056
|
return { isEsm };
|
|
@@ -624,15 +1065,15 @@ function determineFormat(fullPath) {
|
|
|
624
1065
|
return "ts";
|
|
625
1066
|
}
|
|
626
1067
|
async function runPull(options) {
|
|
627
|
-
|
|
1068
|
+
p3.intro(pc4.bgYellow(pc4.black(" pull ")));
|
|
628
1069
|
let fullPath;
|
|
629
1070
|
if (options.config) {
|
|
630
1071
|
fullPath = isAbsolute2(options.config) ? options.config : resolve2(process.cwd(), options.config);
|
|
631
1072
|
} else {
|
|
632
1073
|
const resolved = resolveConfigPath();
|
|
633
1074
|
if (!resolved) {
|
|
634
|
-
|
|
635
|
-
|
|
1075
|
+
p3.log.error(
|
|
1076
|
+
pc4.red("No configuration file found. Run 'owostack init' first.")
|
|
636
1077
|
);
|
|
637
1078
|
process.exit(1);
|
|
638
1079
|
}
|
|
@@ -640,64 +1081,73 @@ async function runPull(options) {
|
|
|
640
1081
|
}
|
|
641
1082
|
const apiKey = getApiKey(options.key);
|
|
642
1083
|
const configSettings = await loadConfigSettings(options.config);
|
|
643
|
-
const
|
|
1084
|
+
const testUrl = getTestApiUrl(configSettings.environments?.test);
|
|
1085
|
+
const liveUrl = getApiUrl(configSettings.environments?.live);
|
|
644
1086
|
const filters = configSettings.filters || {};
|
|
645
1087
|
const format = determineFormat(fullPath);
|
|
646
|
-
const s =
|
|
1088
|
+
const s = p3.spinner();
|
|
647
1089
|
if (options.prod) {
|
|
648
|
-
|
|
649
|
-
const
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
const testPlans = await fetchPlans({
|
|
653
|
-
apiKey,
|
|
654
|
-
apiUrl: `${testUrl}/api/v1`,
|
|
655
|
-
...filters
|
|
656
|
-
});
|
|
657
|
-
s.stop(`Fetched ${testPlans.length} plans from test`);
|
|
658
|
-
s.start(`Fetching from ${pc3.dim("live")}...`);
|
|
659
|
-
const livePlans = await fetchPlans({
|
|
1090
|
+
p3.log.step(pc4.magenta("Production Mode: Pulling from PROD environment"));
|
|
1091
|
+
const apiUrl = `${liveUrl}/api/v1`;
|
|
1092
|
+
s.start(`Fetching plans from ${pc4.dim("prod")}...`);
|
|
1093
|
+
const plans = await fetchPlans({
|
|
660
1094
|
apiKey,
|
|
661
|
-
apiUrl
|
|
1095
|
+
apiUrl,
|
|
662
1096
|
...filters
|
|
663
1097
|
});
|
|
664
|
-
s.stop(`Fetched ${
|
|
1098
|
+
s.stop(`Fetched ${plans.length} plans from prod`);
|
|
665
1099
|
s.start(`Fetching credit systems...`);
|
|
666
|
-
const creditSystems = await fetchCreditSystems(apiKey,
|
|
1100
|
+
const creditSystems = await fetchCreditSystems(apiKey, apiUrl);
|
|
667
1101
|
s.stop(`Fetched ${creditSystems.length} credit systems`);
|
|
1102
|
+
s.start(`Fetching credit packs...`);
|
|
1103
|
+
const creditPacks = await fetchCreditPacks(apiKey, apiUrl);
|
|
1104
|
+
s.stop(`Fetched ${creditPacks.length} credit packs`);
|
|
668
1105
|
const providers = new Set(
|
|
669
|
-
|
|
1106
|
+
plans.map((p9) => p9.provider).filter(Boolean)
|
|
670
1107
|
);
|
|
671
1108
|
const defaultProvider = providers.size === 1 ? Array.from(providers)[0] : void 0;
|
|
672
1109
|
const configContent = generateConfig(
|
|
673
|
-
|
|
1110
|
+
plans,
|
|
674
1111
|
creditSystems,
|
|
1112
|
+
creditPacks,
|
|
675
1113
|
defaultProvider,
|
|
676
1114
|
format
|
|
677
1115
|
);
|
|
678
1116
|
if (options.dryRun) {
|
|
679
|
-
|
|
680
|
-
|
|
1117
|
+
p3.note(configContent, "Generated Config (Dry Run)");
|
|
1118
|
+
printPullSummary(plans, creditSystems, creditPacks);
|
|
1119
|
+
p3.outro(pc4.yellow("Dry run complete. No changes made."));
|
|
681
1120
|
return;
|
|
682
1121
|
}
|
|
683
1122
|
if (existsSync3(fullPath) && !options.force) {
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
1123
|
+
const confirm4 = await p3.confirm({
|
|
1124
|
+
message: `Config file already exists at ${fullPath}. Overwrite?`,
|
|
1125
|
+
initialValue: false
|
|
1126
|
+
});
|
|
1127
|
+
if (p3.isCancel(confirm4) || !confirm4) {
|
|
1128
|
+
p3.outro(pc4.yellow("Operation cancelled"));
|
|
1129
|
+
process.exit(0);
|
|
1130
|
+
}
|
|
687
1131
|
}
|
|
688
1132
|
await writeFile2(fullPath, configContent, "utf8");
|
|
689
|
-
|
|
1133
|
+
p3.log.success(pc4.green(`Wrote configuration to ${fullPath}`));
|
|
1134
|
+
printPullSummary(plans, creditSystems, creditPacks);
|
|
690
1135
|
} else {
|
|
691
|
-
|
|
1136
|
+
p3.log.step(pc4.cyan("Sandbox Mode: Pulling from SANDBOX environment"));
|
|
1137
|
+
const apiUrl = `${testUrl}/api/v1`;
|
|
1138
|
+
s.start(`Fetching plans from ${pc4.dim("sandbox")}...`);
|
|
692
1139
|
const plans = await fetchPlans({
|
|
693
1140
|
apiKey,
|
|
694
|
-
apiUrl
|
|
1141
|
+
apiUrl,
|
|
695
1142
|
...filters
|
|
696
1143
|
});
|
|
697
|
-
s.stop(`Fetched ${plans.length} plans`);
|
|
1144
|
+
s.stop(`Fetched ${plans.length} plans from sandbox`);
|
|
698
1145
|
s.start(`Fetching credit systems...`);
|
|
699
|
-
const creditSystems = await fetchCreditSystems(apiKey,
|
|
1146
|
+
const creditSystems = await fetchCreditSystems(apiKey, apiUrl);
|
|
700
1147
|
s.stop(`Fetched ${creditSystems.length} credit systems`);
|
|
1148
|
+
s.start(`Fetching credit packs...`);
|
|
1149
|
+
const creditPacks = await fetchCreditPacks(apiKey, apiUrl);
|
|
1150
|
+
s.stop(`Fetched ${creditPacks.length} credit packs`);
|
|
701
1151
|
const providers = new Set(
|
|
702
1152
|
plans.map((p9) => p9.provider).filter(Boolean)
|
|
703
1153
|
);
|
|
@@ -705,166 +1155,75 @@ async function runPull(options) {
|
|
|
705
1155
|
const configContent = generateConfig(
|
|
706
1156
|
plans,
|
|
707
1157
|
creditSystems,
|
|
1158
|
+
creditPacks,
|
|
708
1159
|
defaultProvider,
|
|
709
1160
|
format
|
|
710
1161
|
);
|
|
711
1162
|
if (options.dryRun) {
|
|
712
|
-
|
|
713
|
-
|
|
1163
|
+
p3.note(configContent, "Generated Config (Dry Run)");
|
|
1164
|
+
printPullSummary(plans, creditSystems, creditPacks);
|
|
1165
|
+
p3.outro(pc4.yellow("Dry run complete. No changes made."));
|
|
714
1166
|
return;
|
|
715
1167
|
}
|
|
716
1168
|
if (existsSync3(fullPath) && !options.force) {
|
|
717
|
-
const
|
|
1169
|
+
const confirm4 = await p3.confirm({
|
|
718
1170
|
message: `Config file already exists. Overwrite?`,
|
|
719
1171
|
initialValue: false
|
|
720
1172
|
});
|
|
721
|
-
if (
|
|
722
|
-
|
|
1173
|
+
if (p3.isCancel(confirm4) || !confirm4) {
|
|
1174
|
+
p3.outro(pc4.yellow("Operation cancelled"));
|
|
723
1175
|
process.exit(0);
|
|
724
1176
|
}
|
|
725
1177
|
}
|
|
726
1178
|
await writeFile2(fullPath, configContent, "utf8");
|
|
727
|
-
|
|
1179
|
+
p3.log.success(pc4.green(`Wrote configuration to ${fullPath}`));
|
|
1180
|
+
printPullSummary(plans, creditSystems, creditPacks);
|
|
728
1181
|
}
|
|
729
|
-
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
// src/commands/diff.ts
|
|
733
|
-
import * as p4 from "@clack/prompts";
|
|
734
|
-
import pc5 from "picocolors";
|
|
735
|
-
|
|
736
|
-
// src/lib/diff.ts
|
|
737
|
-
import pc4 from "picocolors";
|
|
738
|
-
import * as p3 from "@clack/prompts";
|
|
739
|
-
function normalizeFeature(pf) {
|
|
740
|
-
return {
|
|
741
|
-
slug: pf.slug,
|
|
742
|
-
enabled: pf.enabled,
|
|
743
|
-
limit: pf.limit ?? null,
|
|
744
|
-
// Handle both SDK 'reset' and API 'resetInterval'
|
|
745
|
-
reset: pf.reset || pf.resetInterval || "monthly",
|
|
746
|
-
// Handle both SDK 'overage' and API 'overage' (same name)
|
|
747
|
-
overage: pf.overage || "block",
|
|
748
|
-
overagePrice: pf.overagePrice ?? null
|
|
749
|
-
};
|
|
750
|
-
}
|
|
751
|
-
function normalizePlan(plan) {
|
|
752
|
-
return {
|
|
753
|
-
slug: plan.slug,
|
|
754
|
-
name: plan.name ?? null,
|
|
755
|
-
description: plan.description ?? null,
|
|
756
|
-
price: plan.price ?? 0,
|
|
757
|
-
currency: plan.currency ?? null,
|
|
758
|
-
interval: plan.interval ?? null,
|
|
759
|
-
planGroup: plan.planGroup ?? null,
|
|
760
|
-
trialDays: plan.trialDays ?? 0,
|
|
761
|
-
features: (plan.features || []).map(normalizeFeature).sort((a, b) => a.slug.localeCompare(b.slug))
|
|
762
|
-
};
|
|
1182
|
+
p3.outro(pc4.green("Pull complete! \u2728"));
|
|
763
1183
|
}
|
|
764
|
-
function
|
|
765
|
-
const
|
|
766
|
-
const
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
const onlyLocal = [];
|
|
770
|
-
const onlyRemote = [];
|
|
771
|
-
const changed = [];
|
|
772
|
-
for (const slug of localMap.keys()) {
|
|
773
|
-
if (!remoteMap.has(slug)) onlyLocal.push(slug);
|
|
774
|
-
}
|
|
775
|
-
for (const slug of remoteMap.keys()) {
|
|
776
|
-
if (!localMap.has(slug)) onlyRemote.push(slug);
|
|
777
|
-
}
|
|
778
|
-
for (const slug of localMap.keys()) {
|
|
779
|
-
if (!remoteMap.has(slug)) continue;
|
|
780
|
-
const local = localMap.get(slug);
|
|
781
|
-
const remote = remoteMap.get(slug);
|
|
782
|
-
const details = [];
|
|
783
|
-
const fields = [
|
|
784
|
-
"name",
|
|
785
|
-
"description",
|
|
786
|
-
"price",
|
|
787
|
-
"currency",
|
|
788
|
-
"interval",
|
|
789
|
-
"planGroup",
|
|
790
|
-
"trialDays"
|
|
791
|
-
];
|
|
792
|
-
for (const field of fields) {
|
|
793
|
-
if (local[field] !== remote[field]) {
|
|
794
|
-
const localVal = JSON.stringify(local[field]);
|
|
795
|
-
const remoteVal = JSON.stringify(remote[field]);
|
|
796
|
-
details.push(
|
|
797
|
-
`${String(field)}: ${pc4.green(localVal)} \u2192 ${pc4.red(remoteVal)}`
|
|
798
|
-
);
|
|
799
|
-
}
|
|
800
|
-
}
|
|
801
|
-
const localFeatures = new Map(local.features.map((f) => [f.slug, f]));
|
|
802
|
-
const remoteFeatures = new Map(
|
|
803
|
-
remote.features.map((f) => [f.slug, f])
|
|
804
|
-
);
|
|
805
|
-
for (const fslug of localFeatures.keys()) {
|
|
806
|
-
if (!remoteFeatures.has(fslug)) {
|
|
807
|
-
details.push(`feature ${fslug}: ${pc4.green("[local only]")}`);
|
|
808
|
-
continue;
|
|
809
|
-
}
|
|
810
|
-
const lf = localFeatures.get(fslug);
|
|
811
|
-
const rf = remoteFeatures.get(fslug);
|
|
812
|
-
if (JSON.stringify(lf) !== JSON.stringify(rf)) {
|
|
813
|
-
details.push(`feature ${fslug}:`);
|
|
814
|
-
details.push(` ${pc4.green(JSON.stringify(lf))}`);
|
|
815
|
-
details.push(` ${pc4.red(JSON.stringify(rf))}`);
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
|
-
for (const fslug of remoteFeatures.keys()) {
|
|
819
|
-
if (!localFeatures.has(fslug)) {
|
|
820
|
-
details.push(`feature ${fslug}: ${pc4.red("[remote only]")}`);
|
|
821
|
-
}
|
|
822
|
-
}
|
|
823
|
-
if (details.length > 0) {
|
|
824
|
-
changed.push({ slug, details });
|
|
1184
|
+
function printPullSummary(plans, creditSystems, creditPacks = []) {
|
|
1185
|
+
const featureSlugs = /* @__PURE__ */ new Set();
|
|
1186
|
+
for (const plan of plans) {
|
|
1187
|
+
for (const f of plan.features || []) {
|
|
1188
|
+
featureSlugs.add(f.slug);
|
|
825
1189
|
}
|
|
826
1190
|
}
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
return;
|
|
833
|
-
}
|
|
834
|
-
if (diff.onlyLocal.length > 0) {
|
|
835
|
-
p3.note(
|
|
836
|
-
diff.onlyLocal.map((slug) => `${pc4.green("+")} ${slug}`).join("\n"),
|
|
837
|
-
"Only in Local"
|
|
1191
|
+
const lines = [];
|
|
1192
|
+
for (const plan of plans) {
|
|
1193
|
+
const featureCount = (plan.features || []).length;
|
|
1194
|
+
lines.push(
|
|
1195
|
+
`${pc4.green("\u2193")} ${pc4.bold(plan.slug)} ${pc4.dim(`${plan.currency} ${plan.price}/${plan.interval}`)} ${pc4.dim(`(${featureCount} features)`)} ${plan.isAddon ? pc4.cyan("(addon)") : ""}`
|
|
838
1196
|
);
|
|
839
1197
|
}
|
|
840
|
-
if (
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
1198
|
+
if (creditSystems.length > 0) {
|
|
1199
|
+
lines.push("");
|
|
1200
|
+
for (const cs of creditSystems) {
|
|
1201
|
+
const childCount = (cs.features || []).length;
|
|
1202
|
+
lines.push(
|
|
1203
|
+
`${pc4.green("\u2193")} ${pc4.bold(cs.slug)} ${pc4.dim(`credit system (${childCount} features)`)}`
|
|
1204
|
+
);
|
|
1205
|
+
}
|
|
845
1206
|
}
|
|
846
|
-
if (
|
|
847
|
-
|
|
848
|
-
for (const
|
|
849
|
-
|
|
850
|
-
${pc4.bold(
|
|
851
|
-
|
|
852
|
-
for (const line of item.details) {
|
|
853
|
-
changedText += ` ${line}
|
|
854
|
-
`;
|
|
855
|
-
}
|
|
1207
|
+
if (creditPacks.length > 0) {
|
|
1208
|
+
lines.push("");
|
|
1209
|
+
for (const pack of creditPacks) {
|
|
1210
|
+
lines.push(
|
|
1211
|
+
`${pc4.green("\u2193")} ${pc4.bold(pack.slug)} ${pc4.dim(`${pack.currency} ${pack.price} for ${pack.credits} credits`)} ${pc4.cyan("(pack)")}`
|
|
1212
|
+
);
|
|
856
1213
|
}
|
|
857
|
-
p3.note(changedText.trim(), "Changed Plans");
|
|
858
1214
|
}
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
1215
|
+
p3.note(lines.join("\n"), "Pulled");
|
|
1216
|
+
const counts = [
|
|
1217
|
+
`${pc4.bold(plans.length.toString())} plans`,
|
|
1218
|
+
`${pc4.bold(featureSlugs.size.toString())} features`,
|
|
1219
|
+
creditSystems.length > 0 ? `${pc4.bold(creditSystems.length.toString())} credit systems` : ""
|
|
1220
|
+
].filter(Boolean).join(pc4.dim(" \xB7 "));
|
|
1221
|
+
p3.log.info(counts);
|
|
865
1222
|
}
|
|
866
1223
|
|
|
867
1224
|
// src/commands/diff.ts
|
|
1225
|
+
import * as p4 from "@clack/prompts";
|
|
1226
|
+
import pc5 from "picocolors";
|
|
868
1227
|
async function runDiff(options) {
|
|
869
1228
|
p4.intro(pc5.bgYellow(pc5.black(" diff ")));
|
|
870
1229
|
const fullPath = resolveConfigPath(options.config);
|
|
@@ -874,12 +1233,12 @@ async function runDiff(options) {
|
|
|
874
1233
|
}
|
|
875
1234
|
const apiKey = getApiKey(options.key);
|
|
876
1235
|
const configSettings = await loadConfigSettings(options.config);
|
|
877
|
-
const
|
|
1236
|
+
const testUrl = getTestApiUrl(configSettings.environments?.test);
|
|
1237
|
+
const liveUrl = getApiUrl(configSettings.environments?.live);
|
|
878
1238
|
const s = p4.spinner();
|
|
879
1239
|
if (options.prod) {
|
|
880
|
-
p4.log.step(pc5.magenta("Production Mode: Comparing
|
|
881
|
-
const
|
|
882
|
-
const liveUrl = getApiUrl(configSettings.environments?.live);
|
|
1240
|
+
p4.log.step(pc5.magenta("Production Mode: Comparing with PROD environment"));
|
|
1241
|
+
const apiUrl = `${liveUrl}/api/v1`;
|
|
883
1242
|
s.start("Loading local configuration...");
|
|
884
1243
|
let owo;
|
|
885
1244
|
try {
|
|
@@ -894,18 +1253,34 @@ async function runDiff(options) {
|
|
|
894
1253
|
);
|
|
895
1254
|
process.exit(1);
|
|
896
1255
|
}
|
|
1256
|
+
if (!owo || !owo._config) {
|
|
1257
|
+
s.stop(pc5.red("Invalid configuration"));
|
|
1258
|
+
p4.log.error("Config file must export an Owostack instance.");
|
|
1259
|
+
process.exit(1);
|
|
1260
|
+
}
|
|
897
1261
|
s.stop("Configuration loaded");
|
|
898
1262
|
const { buildSyncPayload } = await import("owostack").catch(() => ({
|
|
899
1263
|
buildSyncPayload: null
|
|
900
1264
|
}));
|
|
901
1265
|
const localPayload = buildSyncPayload(owo._config.catalog);
|
|
902
|
-
|
|
903
|
-
const
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
printDiff(
|
|
1266
|
+
s.start(`Fetching remote catalog from ${pc5.dim("prod")}...`);
|
|
1267
|
+
const livePlans = await fetchPlans({ apiKey, apiUrl });
|
|
1268
|
+
const liveCreditSystems = await fetchCreditSystems(apiKey, apiUrl);
|
|
1269
|
+
const liveCreditPacks = await fetchCreditPacks(apiKey, apiUrl);
|
|
1270
|
+
s.stop("Remote catalog fetched");
|
|
1271
|
+
printDiff(
|
|
1272
|
+
diffPlans(
|
|
1273
|
+
localPayload?.plans ?? [],
|
|
1274
|
+
livePlans,
|
|
1275
|
+
localPayload?.creditSystems ?? [],
|
|
1276
|
+
liveCreditSystems,
|
|
1277
|
+
localPayload?.creditPacks ?? [],
|
|
1278
|
+
liveCreditPacks
|
|
1279
|
+
)
|
|
1280
|
+
);
|
|
908
1281
|
} else {
|
|
1282
|
+
p4.log.step(pc5.cyan("Sandbox Mode: Comparing with SANDBOX environment"));
|
|
1283
|
+
const apiUrl = `${testUrl}/api/v1`;
|
|
909
1284
|
s.start("Loading local configuration...");
|
|
910
1285
|
let owo;
|
|
911
1286
|
try {
|
|
@@ -920,18 +1295,34 @@ async function runDiff(options) {
|
|
|
920
1295
|
);
|
|
921
1296
|
process.exit(1);
|
|
922
1297
|
}
|
|
1298
|
+
if (!owo || !owo._config) {
|
|
1299
|
+
s.stop(pc5.red("Invalid configuration"));
|
|
1300
|
+
p4.log.error("Config file must export an Owostack instance.");
|
|
1301
|
+
process.exit(1);
|
|
1302
|
+
}
|
|
923
1303
|
s.stop("Configuration loaded");
|
|
924
1304
|
const { buildSyncPayload } = await import("owostack").catch(() => ({
|
|
925
1305
|
buildSyncPayload: null
|
|
926
1306
|
}));
|
|
927
1307
|
const localPayload = buildSyncPayload(owo._config.catalog);
|
|
928
|
-
s.start(`Fetching remote
|
|
1308
|
+
s.start(`Fetching remote catalog from ${pc5.dim("sandbox")}...`);
|
|
929
1309
|
const remotePlans = await fetchPlans({
|
|
930
1310
|
apiKey,
|
|
931
|
-
apiUrl
|
|
1311
|
+
apiUrl
|
|
932
1312
|
});
|
|
933
|
-
|
|
934
|
-
|
|
1313
|
+
const remoteCreditSystems = await fetchCreditSystems(apiKey, apiUrl);
|
|
1314
|
+
const remoteCreditPacks = await fetchCreditPacks(apiKey, apiUrl);
|
|
1315
|
+
s.stop("Remote catalog fetched");
|
|
1316
|
+
printDiff(
|
|
1317
|
+
diffPlans(
|
|
1318
|
+
localPayload?.plans ?? [],
|
|
1319
|
+
remotePlans,
|
|
1320
|
+
localPayload?.creditSystems ?? [],
|
|
1321
|
+
remoteCreditSystems,
|
|
1322
|
+
localPayload?.creditPacks ?? [],
|
|
1323
|
+
remoteCreditPacks
|
|
1324
|
+
)
|
|
1325
|
+
);
|
|
935
1326
|
}
|
|
936
1327
|
p4.outro(pc5.green("Diff complete \u2728"));
|
|
937
1328
|
}
|
|
@@ -1046,8 +1437,8 @@ function getProjectInfo2() {
|
|
|
1046
1437
|
const isTs = existsSync4(join3(cwd, "tsconfig.json"));
|
|
1047
1438
|
let isEsm = false;
|
|
1048
1439
|
try {
|
|
1049
|
-
const
|
|
1050
|
-
isEsm =
|
|
1440
|
+
const pkg2 = JSON.parse(readFileSync3(join3(cwd, "package.json"), "utf8"));
|
|
1441
|
+
isEsm = pkg2.type === "module";
|
|
1051
1442
|
} catch {
|
|
1052
1443
|
}
|
|
1053
1444
|
return { isTs, isEsm };
|
|
@@ -1082,11 +1473,11 @@ async function runInit(options) {
|
|
|
1082
1473
|
}
|
|
1083
1474
|
}
|
|
1084
1475
|
if (existsSync4(fullPath) && !options.force) {
|
|
1085
|
-
const
|
|
1476
|
+
const confirm4 = await p6.confirm({
|
|
1086
1477
|
message: `Config file already exists at ${fullPath}. Overwrite?`,
|
|
1087
1478
|
initialValue: false
|
|
1088
1479
|
});
|
|
1089
|
-
if (p6.isCancel(
|
|
1480
|
+
if (p6.isCancel(confirm4) || !confirm4) {
|
|
1090
1481
|
p6.outro(pc7.yellow("Initialization cancelled"));
|
|
1091
1482
|
process.exit(0);
|
|
1092
1483
|
}
|
|
@@ -1094,11 +1485,9 @@ async function runInit(options) {
|
|
|
1094
1485
|
const s = p6.spinner();
|
|
1095
1486
|
s.start("Generating project configuration...");
|
|
1096
1487
|
try {
|
|
1097
|
-
const
|
|
1098
|
-
const
|
|
1099
|
-
|
|
1100
|
-
`${getApiUrl()}/api/v1`
|
|
1101
|
-
);
|
|
1488
|
+
const apiUrl = `${getApiUrl()}/api/v1`;
|
|
1489
|
+
const plans = await fetchPlans({ apiKey, apiUrl });
|
|
1490
|
+
const creditSystems = await fetchCreditSystems(apiKey, apiUrl);
|
|
1102
1491
|
const ext = extname3(fullPath);
|
|
1103
1492
|
const { isEsm } = getProjectInfo2();
|
|
1104
1493
|
let format = "ts";
|
|
@@ -1189,39 +1578,51 @@ async function runValidate(options) {
|
|
|
1189
1578
|
for (const f of payload.features) {
|
|
1190
1579
|
p7.log.message(`${pc8.green("\u2713")} ${f.slug} ${pc8.dim(`(${f.type})`)}`);
|
|
1191
1580
|
}
|
|
1581
|
+
if (payload.creditSystems && payload.creditSystems.length > 0) {
|
|
1582
|
+
p7.log.step(pc8.bold("Credit Systems"));
|
|
1583
|
+
for (const cs of payload.creditSystems) {
|
|
1584
|
+
p7.log.message(
|
|
1585
|
+
`${pc8.green("\u2713")} ${pc8.bold(cs.slug)} ${pc8.dim(`(${cs.features.length} features)`)}`
|
|
1586
|
+
);
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1192
1589
|
p7.log.step(pc8.bold("Plans"));
|
|
1193
1590
|
for (const p_obj of payload.plans) {
|
|
1194
1591
|
p7.log.message(
|
|
1195
|
-
`${pc8.green("\u2713")} ${pc8.bold(p_obj.slug)} ${pc8.dim(`${p_obj.currency} ${p_obj.price} / ${p_obj.interval}`)}`
|
|
1592
|
+
`${pc8.green("\u2713")} ${p_obj.isAddon ? pc8.cyan("(addon)") : ""} ${pc8.bold(p_obj.slug)} ${pc8.dim(`${p_obj.currency} ${p_obj.price} / ${p_obj.interval}`)}`
|
|
1196
1593
|
);
|
|
1197
1594
|
}
|
|
1595
|
+
const configSettings = await loadConfigSettings(options.config);
|
|
1596
|
+
const testUrl = getTestApiUrl(configSettings.environments?.test);
|
|
1597
|
+
const liveUrl = getApiUrl(configSettings.environments?.live);
|
|
1598
|
+
const apiKey = getApiKey();
|
|
1198
1599
|
if (options.prod) {
|
|
1199
|
-
p7.log.step(pc8.magenta("Production Mode
|
|
1200
|
-
const
|
|
1201
|
-
const testUrl = getTestApiUrl(configSettings.environments?.test);
|
|
1202
|
-
const liveUrl = getApiUrl(configSettings.environments?.live);
|
|
1203
|
-
const apiKey = getApiKey();
|
|
1600
|
+
p7.log.step(pc8.magenta("Production Mode: Checking PROD environment"));
|
|
1601
|
+
const apiUrl = `${liveUrl}/api/v1`;
|
|
1204
1602
|
try {
|
|
1205
|
-
const
|
|
1603
|
+
const livePlans = await fetchPlans({
|
|
1206
1604
|
apiKey,
|
|
1207
|
-
apiUrl
|
|
1605
|
+
apiUrl
|
|
1208
1606
|
});
|
|
1209
1607
|
p7.log.success(
|
|
1210
|
-
`
|
|
1608
|
+
`PROD environment accessible (${livePlans.length} remote plans)`
|
|
1211
1609
|
);
|
|
1212
1610
|
} catch (e) {
|
|
1213
|
-
p7.log.error(`
|
|
1611
|
+
p7.log.error(`PROD environment check failed: ${e.message}`);
|
|
1214
1612
|
}
|
|
1613
|
+
} else {
|
|
1614
|
+
p7.log.step(pc8.cyan("Sandbox Mode: Checking SANDBOX environment"));
|
|
1615
|
+
const apiUrl = `${testUrl}/api/v1`;
|
|
1215
1616
|
try {
|
|
1216
|
-
const
|
|
1617
|
+
const testPlans = await fetchPlans({
|
|
1217
1618
|
apiKey,
|
|
1218
|
-
apiUrl
|
|
1619
|
+
apiUrl
|
|
1219
1620
|
});
|
|
1220
1621
|
p7.log.success(
|
|
1221
|
-
`
|
|
1622
|
+
`SANDBOX environment accessible (${testPlans.length} remote plans)`
|
|
1222
1623
|
);
|
|
1223
1624
|
} catch (e) {
|
|
1224
|
-
p7.log.error(`
|
|
1625
|
+
p7.log.error(`SANDBOX environment check failed: ${e.message}`);
|
|
1225
1626
|
}
|
|
1226
1627
|
}
|
|
1227
1628
|
p7.outro(pc8.green("Validation passed! \u2728"));
|
|
@@ -1272,25 +1673,26 @@ ${pc9.dim("Config:")} ${GLOBAL_CONFIG_PATH}`,
|
|
|
1272
1673
|
// src/lib/brand.ts
|
|
1273
1674
|
import pc10 from "picocolors";
|
|
1274
1675
|
var OWO_ASCII = `
|
|
1275
|
-
|
|
1276
|
-
${pc10.
|
|
1277
|
-
${pc10.
|
|
1278
|
-
${pc10.
|
|
1279
|
-
${pc10.yellow("\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D")} ${pc10.white("\u255A\u2588\u2588\u2588\u2554\u2588\u2588\u2588\u2554\u255D")} ${pc10.yellow("\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D")}
|
|
1280
|
-
${pc10.yellow("\u255A\u2550\u2550\u2550\u2550\u2550\u255D")} ${pc10.white("\u255A\u2550\u2550\u255D\u255A\u2550\u2550\u255D")} ${pc10.yellow("\u255A\u2550\u2550\u2550\u2550\u2550\u255D")}
|
|
1676
|
+
${pc10.dim("\u2591\u2591\u2588\u2588\u2588\u2591\u2591")} ${pc10.yellow("\u2591\u2591\u2591 \u2591\u2591\u2591")} ${pc10.dim("\u2591\u2591\u2588\u2588\u2588\u2591\u2591")}
|
|
1677
|
+
${pc10.dim("\u2591\u2588 \u2588\u2591")} ${pc10.yellow("\u2591\u2588\u2591 \u2591\u2588\u2591")} ${pc10.dim("\u2591\u2588 \u2588\u2591")}
|
|
1678
|
+
${pc10.dim("\u2591\u2588 \u2588\u2591")} ${pc10.yellow("\u2591\u2588\u2591 \u2588\u2591 \u2591\u2588\u2591")} ${pc10.dim("\u2591\u2588 \u2588\u2591")}
|
|
1679
|
+
${pc10.dim("\u2591\u2591\u2588\u2588\u2588\u2591\u2591")} ${pc10.yellow("\u2591\u2591\u2588\u2591\u2588\u2591\u2588\u2591\u2591")} ${pc10.dim("\u2591\u2591\u2588\u2588\u2588\u2591\u2591")}
|
|
1281
1680
|
`;
|
|
1282
1681
|
function printBrand() {
|
|
1283
1682
|
console.log(OWO_ASCII);
|
|
1284
1683
|
}
|
|
1285
1684
|
|
|
1286
1685
|
// src/index.ts
|
|
1686
|
+
import { createRequire } from "module";
|
|
1687
|
+
var require2 = createRequire(import.meta.url);
|
|
1688
|
+
var pkg = require2("../package.json");
|
|
1287
1689
|
var program = new Command();
|
|
1288
1690
|
printBrand();
|
|
1289
|
-
program.name("owostack").description("CLI for Owostack billing infrastructure").version(
|
|
1290
|
-
program.command("sync").description("Push catalog to the API").option("--config <path>", "Path to config file").option("--key <api-key>", "API secret key").option("--
|
|
1291
|
-
program.command("pull").description("Pull plans from dashboard into owo.config.ts").option("--config <path>", "Path to config file").option("--key <api-key>", "API secret key").option("--force", "Overwrite existing config file", false).option("--
|
|
1292
|
-
program.command("diff").description("Compare local config to dashboard plans").option("--config <path>", "Path to config file").option("--key <api-key>", "API secret key").
|
|
1691
|
+
program.name("owostack").description("CLI for Owostack billing infrastructure").version(pkg.version).option("--prod", "Execute in production environment (default: sandbox)");
|
|
1692
|
+
program.command("sync").description("Push catalog to the API").option("--config <path>", "Path to config file").option("--key <api-key>", "API secret key").option("--dry-run", "Show what would change without applying").option("--yes", "Auto-approve changes without interactive prompt").action((options) => runSync({ ...options, ...program.opts() }));
|
|
1693
|
+
program.command("pull").description("Pull plans from dashboard into owo.config.ts").option("--config <path>", "Path to config file").option("--key <api-key>", "API secret key").option("--force", "Overwrite existing config file", false).option("--dry-run", "Show what would change without applying").action((options) => runPull({ ...options, ...program.opts() }));
|
|
1694
|
+
program.command("diff").description("Compare local config to dashboard plans").option("--config <path>", "Path to config file").option("--key <api-key>", "API secret key").action((options) => runDiff({ ...options, ...program.opts() }));
|
|
1293
1695
|
program.command("init").description("Initialize owo.config.ts from dashboard").option("--config <path>", "Path to config file").option("--key <api-key>", "API secret key").option("--force", "Overwrite existing config file", false).action(runInit);
|
|
1294
|
-
program.command("validate").description("Validate local config without syncing").option("--config <path>", "Path to config file").
|
|
1696
|
+
program.command("validate").description("Validate local config without syncing").option("--config <path>", "Path to config file").action((options) => runValidate({ ...options, ...program.opts() }));
|
|
1295
1697
|
program.command("connect").description("Connect CLI to dashboard via browser").option("--no-browser", "Don't open the browser automatically").action(runConnect);
|
|
1296
1698
|
program.parse();
|