owosk 0.1.2 → 0.1.4
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 +2 -2
- package/dist/index.js +414 -167
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ npx owo --help
|
|
|
20
20
|
|
|
21
21
|
### `init`
|
|
22
22
|
|
|
23
|
-
Initialize a new Owostack project with a default `owo.config.ts
|
|
23
|
+
Initialize a new Owostack project with a default configuration file (`owo.config.ts` or `owo.config.js`).
|
|
24
24
|
|
|
25
25
|
```bash
|
|
26
26
|
npx owo init
|
|
@@ -52,7 +52,7 @@ npx owo diff
|
|
|
52
52
|
|
|
53
53
|
### `validate`
|
|
54
54
|
|
|
55
|
-
Check your local
|
|
55
|
+
Check your local configuration for errors without applying changes.
|
|
56
56
|
|
|
57
57
|
```bash
|
|
58
58
|
npx owo validate
|
package/dist/index.js
CHANGED
|
@@ -46,17 +46,41 @@ function getApiKey(cliKey) {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
// src/lib/loader.ts
|
|
49
|
-
import { resolve, isAbsolute } from "path";
|
|
50
|
-
import { pathToFileURL } from "url";
|
|
49
|
+
import { resolve, isAbsolute, extname } from "path";
|
|
51
50
|
import { existsSync as existsSync2 } from "fs";
|
|
51
|
+
import { createJiti } from "jiti";
|
|
52
|
+
var jiti = createJiti(import.meta.url);
|
|
53
|
+
var DEFAULT_CONFIG_NAMES = [
|
|
54
|
+
"owo.config.ts",
|
|
55
|
+
"owo.config.js",
|
|
56
|
+
"owo.config.mjs",
|
|
57
|
+
"owo.config.cjs",
|
|
58
|
+
"owo.config.mts",
|
|
59
|
+
"owo.config.cts"
|
|
60
|
+
];
|
|
52
61
|
function resolveConfigPath(configPath) {
|
|
53
|
-
|
|
62
|
+
if (configPath) {
|
|
63
|
+
const fullPath = isAbsolute(configPath) ? configPath : resolve(process.cwd(), configPath);
|
|
64
|
+
if (existsSync2(fullPath)) return fullPath;
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
for (const name of DEFAULT_CONFIG_NAMES) {
|
|
68
|
+
const fullPath = resolve(process.cwd(), name);
|
|
69
|
+
if (existsSync2(fullPath)) return fullPath;
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
54
72
|
}
|
|
55
73
|
async function loadOwostackFromConfig(fullPath) {
|
|
56
|
-
let configModule;
|
|
57
74
|
try {
|
|
58
|
-
const
|
|
59
|
-
|
|
75
|
+
const configModule = await jiti.import(fullPath);
|
|
76
|
+
const instance = configModule.default || configModule.owo || configModule;
|
|
77
|
+
if (instance && typeof instance.sync === "function") {
|
|
78
|
+
return instance;
|
|
79
|
+
}
|
|
80
|
+
if (typeof configModule.sync === "function") {
|
|
81
|
+
return configModule;
|
|
82
|
+
}
|
|
83
|
+
return null;
|
|
60
84
|
} catch (e) {
|
|
61
85
|
console.error(`
|
|
62
86
|
\u274C Failed to load config from ${fullPath}`);
|
|
@@ -65,23 +89,36 @@ async function loadOwostackFromConfig(fullPath) {
|
|
|
65
89
|
console.error(
|
|
66
90
|
` Make sure the file exports an Owostack instance as default or named 'owo'.`
|
|
67
91
|
);
|
|
68
|
-
|
|
92
|
+
const ext = extname(fullPath);
|
|
93
|
+
const isTs = ext === ".ts" || ext === ".mts" || ext === ".cts";
|
|
94
|
+
if (isTs) {
|
|
95
|
+
console.error(` Example owo.config.ts:
|
|
69
96
|
`);
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
97
|
+
console.error(
|
|
98
|
+
` import { Owostack, metered, boolean, plan } from "owostack";`
|
|
99
|
+
);
|
|
100
|
+
console.error(
|
|
101
|
+
` export default new Owostack({ secretKey: "...", catalog: [...] });
|
|
75
102
|
`
|
|
76
|
-
|
|
103
|
+
);
|
|
104
|
+
} else {
|
|
105
|
+
console.error(` Example owo.config.js:
|
|
106
|
+
`);
|
|
107
|
+
console.error(
|
|
108
|
+
` const { Owostack, metered, boolean, plan } = require("owostack");`
|
|
109
|
+
);
|
|
110
|
+
console.error(
|
|
111
|
+
` module.exports = new Owostack({ secretKey: "...", catalog: [...] });
|
|
112
|
+
`
|
|
113
|
+
);
|
|
114
|
+
}
|
|
77
115
|
process.exit(1);
|
|
78
116
|
}
|
|
79
|
-
return configModule.default || configModule.owo;
|
|
80
117
|
}
|
|
81
118
|
async function loadConfigSettings(configPath) {
|
|
82
119
|
try {
|
|
83
120
|
const fullPath = resolveConfigPath(configPath);
|
|
84
|
-
if (!
|
|
121
|
+
if (!fullPath) return {};
|
|
85
122
|
const owo = await loadOwostackFromConfig(fullPath);
|
|
86
123
|
if (!owo || !owo._config) return {};
|
|
87
124
|
return {
|
|
@@ -90,7 +127,7 @@ async function loadConfigSettings(configPath) {
|
|
|
90
127
|
filters: owo._config.filters,
|
|
91
128
|
connect: owo._config.connect
|
|
92
129
|
};
|
|
93
|
-
} catch {
|
|
130
|
+
} catch (e) {
|
|
94
131
|
return {};
|
|
95
132
|
}
|
|
96
133
|
}
|
|
@@ -100,9 +137,34 @@ async function runSyncSingle(options) {
|
|
|
100
137
|
const { configPath, dryRun, apiUrl } = options;
|
|
101
138
|
const apiKey = getApiKey(options.apiKey);
|
|
102
139
|
const fullPath = resolveConfigPath(configPath);
|
|
140
|
+
if (!fullPath) {
|
|
141
|
+
p.log.error(
|
|
142
|
+
pc.red(
|
|
143
|
+
`Configuration file not found.${configPath ? ` looked at ${configPath}` : " searched defaults."}`
|
|
144
|
+
)
|
|
145
|
+
);
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
103
148
|
const s = p.spinner();
|
|
104
|
-
s.start(`Loading ${pc.cyan(
|
|
105
|
-
|
|
149
|
+
s.start(`Loading ${pc.cyan(fullPath)}`);
|
|
150
|
+
let owo;
|
|
151
|
+
try {
|
|
152
|
+
owo = await loadOwostackFromConfig(fullPath);
|
|
153
|
+
} catch (e) {
|
|
154
|
+
s.stop(pc.red("Failed to load configuration"));
|
|
155
|
+
p.log.error(pc.red(`Error: ${e.message}`));
|
|
156
|
+
p.note(
|
|
157
|
+
`import { Owostack, metered, boolean, plan } from "owostack";
|
|
158
|
+
export default new Owostack({ secretKey: "...", catalog: [...] });`,
|
|
159
|
+
"Example owo.config.ts"
|
|
160
|
+
);
|
|
161
|
+
p.log.info(
|
|
162
|
+
pc.dim(
|
|
163
|
+
"Make sure 'owostack' is installed in your project: 'npm install owostack'"
|
|
164
|
+
)
|
|
165
|
+
);
|
|
166
|
+
process.exit(1);
|
|
167
|
+
}
|
|
106
168
|
if (!owo || typeof owo.sync !== "function") {
|
|
107
169
|
s.stop(pc.red("Invalid configuration"));
|
|
108
170
|
p.log.error("Config file must export an Owostack instance.");
|
|
@@ -223,11 +285,13 @@ async function runSync(options) {
|
|
|
223
285
|
|
|
224
286
|
// src/commands/pull.ts
|
|
225
287
|
import * as p2 from "@clack/prompts";
|
|
226
|
-
import
|
|
227
|
-
import { existsSync as existsSync3 } from "fs";
|
|
288
|
+
import pc3 from "picocolors";
|
|
289
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
228
290
|
import { writeFile as writeFile2 } from "fs/promises";
|
|
291
|
+
import { join as join2, resolve as resolve2, extname as extname2, isAbsolute as isAbsolute2 } from "path";
|
|
229
292
|
|
|
230
293
|
// src/lib/api.ts
|
|
294
|
+
import pc2 from "picocolors";
|
|
231
295
|
async function fetchPlans(options) {
|
|
232
296
|
if (!options.apiKey) {
|
|
233
297
|
console.error(
|
|
@@ -242,19 +306,40 @@ async function fetchPlans(options) {
|
|
|
242
306
|
if (options.interval) url.searchParams.set("interval", options.interval);
|
|
243
307
|
if (options.currency) url.searchParams.set("currency", options.currency);
|
|
244
308
|
if (options.includeInactive) url.searchParams.set("includeInactive", "true");
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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(`
|
|
253
318
|
\u274C Failed to fetch plans: ${message}
|
|
254
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
|
+
}
|
|
255
341
|
process.exit(1);
|
|
256
342
|
}
|
|
257
|
-
return data?.plans || [];
|
|
258
343
|
}
|
|
259
344
|
async function fetchCreditSystems(apiKey, apiUrl) {
|
|
260
345
|
if (!apiKey) {
|
|
@@ -329,7 +414,9 @@ function slugToIdentifier(slug, used) {
|
|
|
329
414
|
used.add(candidate);
|
|
330
415
|
return candidate;
|
|
331
416
|
}
|
|
332
|
-
function generateConfig(plans, creditSystems = [], defaultProvider) {
|
|
417
|
+
function generateConfig(plans, creditSystems = [], defaultProvider, format = "ts") {
|
|
418
|
+
const isTs = format === "ts";
|
|
419
|
+
const isCjs = format === "cjs";
|
|
333
420
|
const creditSystemSlugs = new Set(creditSystems.map((cs) => cs.slug));
|
|
334
421
|
const creditSystemBySlug = new Map(creditSystems.map((cs) => [cs.slug, cs]));
|
|
335
422
|
const featuresBySlug = /* @__PURE__ */ new Map();
|
|
@@ -340,7 +427,8 @@ function generateConfig(plans, creditSystems = [], defaultProvider) {
|
|
|
340
427
|
featuresBySlug.set(f.slug, {
|
|
341
428
|
slug: f.slug,
|
|
342
429
|
name: f.name,
|
|
343
|
-
type: f.type || "metered"
|
|
430
|
+
type: f.type || "metered",
|
|
431
|
+
meterType: f.meterType
|
|
344
432
|
});
|
|
345
433
|
}
|
|
346
434
|
}
|
|
@@ -363,14 +451,14 @@ function generateConfig(plans, creditSystems = [], defaultProvider) {
|
|
|
363
451
|
const varName = slugToIdentifier(feature.slug, usedNames);
|
|
364
452
|
featureVars.set(feature.slug, varName);
|
|
365
453
|
const nameArg = feature.name ? `, { name: ${JSON.stringify(feature.name)} }` : "";
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
454
|
+
const isEntity = feature.meterType === "non_consumable";
|
|
455
|
+
const builder = feature.type === "boolean" ? "boolean" : isEntity ? "entity" : "metered";
|
|
456
|
+
const decl = `${builder}(${JSON.stringify(feature.slug)}${nameArg})`;
|
|
457
|
+
if (isCjs) {
|
|
458
|
+
featureLines.push(`const ${varName} = ${decl};`);
|
|
459
|
+
featureLines.push(`exports.${varName} = ${varName};`);
|
|
370
460
|
} else {
|
|
371
|
-
featureLines.push(
|
|
372
|
-
`export const ${varName} = metered(${JSON.stringify(feature.slug)}${nameArg});`
|
|
373
|
-
);
|
|
461
|
+
featureLines.push(`export const ${varName} = ${decl};`);
|
|
374
462
|
}
|
|
375
463
|
}
|
|
376
464
|
const creditSystemLines = [];
|
|
@@ -389,9 +477,13 @@ function generateConfig(plans, creditSystems = [], defaultProvider) {
|
|
|
389
477
|
descArg,
|
|
390
478
|
`features: [${featureEntries.join(", ")}]`
|
|
391
479
|
].filter(Boolean);
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
480
|
+
const decl = `creditSystem(${JSON.stringify(cs.slug)}, { ${optsParts.join(", ")} })`;
|
|
481
|
+
if (isCjs) {
|
|
482
|
+
creditSystemLines.push(`const ${varName} = ${decl};`);
|
|
483
|
+
creditSystemLines.push(`exports.${varName} = ${varName};`);
|
|
484
|
+
} else {
|
|
485
|
+
creditSystemLines.push(`export const ${varName} = ${decl};`);
|
|
486
|
+
}
|
|
395
487
|
}
|
|
396
488
|
const planLines = [];
|
|
397
489
|
for (const plan of plans) {
|
|
@@ -447,9 +539,13 @@ function generateConfig(plans, creditSystems = [], defaultProvider) {
|
|
|
447
539
|
featureEntries.push(`${varName}.config(${JSON.stringify(config2)})`);
|
|
448
540
|
continue;
|
|
449
541
|
}
|
|
542
|
+
const isEntityFeature = globalFeature?.meterType === "non_consumable";
|
|
450
543
|
const config = {};
|
|
451
544
|
if (pf.limit !== void 0) config.limit = pf.limit;
|
|
452
|
-
|
|
545
|
+
if (!isEntityFeature) {
|
|
546
|
+
const reset = pf.resetInterval || pf.reset || "monthly";
|
|
547
|
+
if (reset !== "none") config.reset = reset;
|
|
548
|
+
}
|
|
453
549
|
if (pf.overage) config.overage = pf.overage;
|
|
454
550
|
if (pf.overagePrice !== void 0) config.overagePrice = pf.overagePrice;
|
|
455
551
|
const configKeys = Object.keys(config);
|
|
@@ -475,46 +571,91 @@ function generateConfig(plans, creditSystems = [], defaultProvider) {
|
|
|
475
571
|
);
|
|
476
572
|
}
|
|
477
573
|
const hasCreditSystems = creditSystemLines.length > 0;
|
|
574
|
+
const hasEntities = Array.from(featuresBySlug.values()).some(
|
|
575
|
+
(f) => f.meterType === "non_consumable"
|
|
576
|
+
);
|
|
478
577
|
const providerLine = defaultProvider ? ` provider: ${JSON.stringify(defaultProvider)},
|
|
479
578
|
` : "";
|
|
579
|
+
const importParts = ["Owostack", "metered", "boolean"];
|
|
580
|
+
if (hasEntities) importParts.push("entity");
|
|
581
|
+
importParts.push("creditSystem", "plan");
|
|
582
|
+
const imports = isCjs ? `const { ${importParts.join(", ")} } = require("owostack");` : `import { ${importParts.join(", ")} } from "owostack";`;
|
|
583
|
+
const tsCheck = !isTs ? `// @ts-check` : "";
|
|
584
|
+
const jsDoc = !isTs ? `/** @type {import('owostack').Owostack} */` : "";
|
|
585
|
+
const owoDecl = isCjs ? "exports.owo =" : "export const owo =";
|
|
586
|
+
const secretKey = isTs ? "process.env.OWOSTACK_SECRET_KEY!" : "process.env.OWOSTACK_SECRET_KEY";
|
|
480
587
|
return [
|
|
481
|
-
|
|
588
|
+
tsCheck,
|
|
589
|
+
imports,
|
|
482
590
|
``,
|
|
483
591
|
...featureLines,
|
|
484
592
|
...hasCreditSystems ? ["", ...creditSystemLines] : [],
|
|
485
593
|
``,
|
|
486
|
-
|
|
487
|
-
|
|
594
|
+
jsDoc,
|
|
595
|
+
`${owoDecl} new Owostack({`,
|
|
596
|
+
` secretKey: ${secretKey},`,
|
|
488
597
|
providerLine,
|
|
489
598
|
` catalog: [`,
|
|
490
599
|
` ${planLines.join(",\n ")}`,
|
|
491
600
|
` ],`,
|
|
492
601
|
`});`,
|
|
493
602
|
``
|
|
494
|
-
].join("\n");
|
|
603
|
+
].filter(Boolean).join("\n");
|
|
495
604
|
}
|
|
496
605
|
|
|
497
606
|
// src/commands/pull.ts
|
|
607
|
+
function getProjectInfo() {
|
|
608
|
+
const cwd = process.cwd();
|
|
609
|
+
let isEsm = false;
|
|
610
|
+
try {
|
|
611
|
+
const pkg = JSON.parse(readFileSync2(join2(cwd, "package.json"), "utf8"));
|
|
612
|
+
isEsm = pkg.type === "module";
|
|
613
|
+
} catch {
|
|
614
|
+
}
|
|
615
|
+
return { isEsm };
|
|
616
|
+
}
|
|
617
|
+
function determineFormat(fullPath) {
|
|
618
|
+
const ext = extname2(fullPath);
|
|
619
|
+
const { isEsm } = getProjectInfo();
|
|
620
|
+
if (ext === ".ts" || ext === ".mts" || ext === ".cts") return "ts";
|
|
621
|
+
if (ext === ".mjs") return "esm";
|
|
622
|
+
if (ext === ".cjs") return "cjs";
|
|
623
|
+
if (ext === ".js") return isEsm ? "esm" : "cjs";
|
|
624
|
+
return "ts";
|
|
625
|
+
}
|
|
498
626
|
async function runPull(options) {
|
|
499
|
-
p2.intro(
|
|
500
|
-
|
|
627
|
+
p2.intro(pc3.bgYellow(pc3.black(" pull ")));
|
|
628
|
+
let fullPath;
|
|
629
|
+
if (options.config) {
|
|
630
|
+
fullPath = isAbsolute2(options.config) ? options.config : resolve2(process.cwd(), options.config);
|
|
631
|
+
} else {
|
|
632
|
+
const resolved = resolveConfigPath();
|
|
633
|
+
if (!resolved) {
|
|
634
|
+
p2.log.error(
|
|
635
|
+
pc3.red("No configuration file found. Run 'owostack init' first.")
|
|
636
|
+
);
|
|
637
|
+
process.exit(1);
|
|
638
|
+
}
|
|
639
|
+
fullPath = resolved;
|
|
640
|
+
}
|
|
501
641
|
const apiKey = getApiKey(options.key);
|
|
502
642
|
const configSettings = await loadConfigSettings(options.config);
|
|
503
643
|
const baseUrl = getApiUrl(configSettings.apiUrl);
|
|
504
644
|
const filters = configSettings.filters || {};
|
|
645
|
+
const format = determineFormat(fullPath);
|
|
505
646
|
const s = p2.spinner();
|
|
506
647
|
if (options.prod) {
|
|
507
|
-
p2.log.step(
|
|
648
|
+
p2.log.step(pc3.magenta("Production Mode: Fetching both environments"));
|
|
508
649
|
const testUrl = getTestApiUrl(configSettings.environments?.test);
|
|
509
650
|
const liveUrl = getApiUrl(configSettings.environments?.live);
|
|
510
|
-
s.start(`Fetching from ${
|
|
651
|
+
s.start(`Fetching from ${pc3.dim("test")}...`);
|
|
511
652
|
const testPlans = await fetchPlans({
|
|
512
653
|
apiKey,
|
|
513
654
|
apiUrl: `${testUrl}/api/v1`,
|
|
514
655
|
...filters
|
|
515
656
|
});
|
|
516
657
|
s.stop(`Fetched ${testPlans.length} plans from test`);
|
|
517
|
-
s.start(`Fetching from ${
|
|
658
|
+
s.start(`Fetching from ${pc3.dim("live")}...`);
|
|
518
659
|
const livePlans = await fetchPlans({
|
|
519
660
|
apiKey,
|
|
520
661
|
apiUrl: `${liveUrl}/api/v1`,
|
|
@@ -531,22 +672,23 @@ async function runPull(options) {
|
|
|
531
672
|
const configContent = generateConfig(
|
|
532
673
|
livePlans,
|
|
533
674
|
creditSystems,
|
|
534
|
-
defaultProvider
|
|
675
|
+
defaultProvider,
|
|
676
|
+
format
|
|
535
677
|
);
|
|
536
678
|
if (options.dryRun) {
|
|
537
679
|
p2.note(configContent, "Generated Config (Dry Run)");
|
|
538
|
-
p2.outro(
|
|
680
|
+
p2.outro(pc3.yellow("Dry run complete. No changes made."));
|
|
539
681
|
return;
|
|
540
682
|
}
|
|
541
683
|
if (existsSync3(fullPath) && !options.force) {
|
|
542
|
-
p2.log.error(
|
|
543
|
-
p2.log.info(
|
|
684
|
+
p2.log.error(pc3.red(`Config file already exists at ${fullPath}`));
|
|
685
|
+
p2.log.info(pc3.dim("Use --force to overwrite."));
|
|
544
686
|
process.exit(1);
|
|
545
687
|
}
|
|
546
688
|
await writeFile2(fullPath, configContent, "utf8");
|
|
547
|
-
p2.log.success(
|
|
689
|
+
p2.log.success(pc3.green(`Wrote configuration to ${fullPath}`));
|
|
548
690
|
} else {
|
|
549
|
-
s.start(`Fetching plans from ${
|
|
691
|
+
s.start(`Fetching plans from ${pc3.dim(baseUrl)}...`);
|
|
550
692
|
const plans = await fetchPlans({
|
|
551
693
|
apiKey,
|
|
552
694
|
apiUrl: `${baseUrl}/api/v1`,
|
|
@@ -560,10 +702,15 @@ async function runPull(options) {
|
|
|
560
702
|
plans.map((p9) => p9.provider).filter(Boolean)
|
|
561
703
|
);
|
|
562
704
|
const defaultProvider = providers.size === 1 ? Array.from(providers)[0] : void 0;
|
|
563
|
-
const configContent = generateConfig(
|
|
705
|
+
const configContent = generateConfig(
|
|
706
|
+
plans,
|
|
707
|
+
creditSystems,
|
|
708
|
+
defaultProvider,
|
|
709
|
+
format
|
|
710
|
+
);
|
|
564
711
|
if (options.dryRun) {
|
|
565
712
|
p2.note(configContent, "Generated Config (Dry Run)");
|
|
566
|
-
p2.outro(
|
|
713
|
+
p2.outro(pc3.yellow("Dry run complete. No changes made."));
|
|
567
714
|
return;
|
|
568
715
|
}
|
|
569
716
|
if (existsSync3(fullPath) && !options.force) {
|
|
@@ -572,22 +719,22 @@ async function runPull(options) {
|
|
|
572
719
|
initialValue: false
|
|
573
720
|
});
|
|
574
721
|
if (p2.isCancel(confirm3) || !confirm3) {
|
|
575
|
-
p2.outro(
|
|
722
|
+
p2.outro(pc3.yellow("Operation cancelled"));
|
|
576
723
|
process.exit(0);
|
|
577
724
|
}
|
|
578
725
|
}
|
|
579
726
|
await writeFile2(fullPath, configContent, "utf8");
|
|
580
|
-
p2.log.success(
|
|
727
|
+
p2.log.success(pc3.green(`Wrote configuration to ${fullPath}`));
|
|
581
728
|
}
|
|
582
|
-
p2.outro(
|
|
729
|
+
p2.outro(pc3.green("Pull complete! \u2728"));
|
|
583
730
|
}
|
|
584
731
|
|
|
585
732
|
// src/commands/diff.ts
|
|
586
733
|
import * as p4 from "@clack/prompts";
|
|
587
|
-
import
|
|
734
|
+
import pc5 from "picocolors";
|
|
588
735
|
|
|
589
736
|
// src/lib/diff.ts
|
|
590
|
-
import
|
|
737
|
+
import pc4 from "picocolors";
|
|
591
738
|
import * as p3 from "@clack/prompts";
|
|
592
739
|
function normalizeFeature(pf) {
|
|
593
740
|
return {
|
|
@@ -647,7 +794,7 @@ function diffPlans(localPlans, remotePlans) {
|
|
|
647
794
|
const localVal = JSON.stringify(local[field]);
|
|
648
795
|
const remoteVal = JSON.stringify(remote[field]);
|
|
649
796
|
details.push(
|
|
650
|
-
`${String(field)}: ${
|
|
797
|
+
`${String(field)}: ${pc4.green(localVal)} \u2192 ${pc4.red(remoteVal)}`
|
|
651
798
|
);
|
|
652
799
|
}
|
|
653
800
|
}
|
|
@@ -657,20 +804,20 @@ function diffPlans(localPlans, remotePlans) {
|
|
|
657
804
|
);
|
|
658
805
|
for (const fslug of localFeatures.keys()) {
|
|
659
806
|
if (!remoteFeatures.has(fslug)) {
|
|
660
|
-
details.push(`feature ${fslug}: ${
|
|
807
|
+
details.push(`feature ${fslug}: ${pc4.green("[local only]")}`);
|
|
661
808
|
continue;
|
|
662
809
|
}
|
|
663
810
|
const lf = localFeatures.get(fslug);
|
|
664
811
|
const rf = remoteFeatures.get(fslug);
|
|
665
812
|
if (JSON.stringify(lf) !== JSON.stringify(rf)) {
|
|
666
813
|
details.push(`feature ${fslug}:`);
|
|
667
|
-
details.push(` ${
|
|
668
|
-
details.push(` ${
|
|
814
|
+
details.push(` ${pc4.green(JSON.stringify(lf))}`);
|
|
815
|
+
details.push(` ${pc4.red(JSON.stringify(rf))}`);
|
|
669
816
|
}
|
|
670
817
|
}
|
|
671
818
|
for (const fslug of remoteFeatures.keys()) {
|
|
672
819
|
if (!localFeatures.has(fslug)) {
|
|
673
|
-
details.push(`feature ${fslug}: ${
|
|
820
|
+
details.push(`feature ${fslug}: ${pc4.red("[remote only]")}`);
|
|
674
821
|
}
|
|
675
822
|
}
|
|
676
823
|
if (details.length > 0) {
|
|
@@ -681,18 +828,18 @@ function diffPlans(localPlans, remotePlans) {
|
|
|
681
828
|
}
|
|
682
829
|
function printDiff(diff) {
|
|
683
830
|
if (diff.onlyLocal.length === 0 && diff.onlyRemote.length === 0 && diff.changed.length === 0) {
|
|
684
|
-
p3.log.success(
|
|
831
|
+
p3.log.success(pc4.green("No differences found."));
|
|
685
832
|
return;
|
|
686
833
|
}
|
|
687
834
|
if (diff.onlyLocal.length > 0) {
|
|
688
835
|
p3.note(
|
|
689
|
-
diff.onlyLocal.map((slug) => `${
|
|
836
|
+
diff.onlyLocal.map((slug) => `${pc4.green("+")} ${slug}`).join("\n"),
|
|
690
837
|
"Only in Local"
|
|
691
838
|
);
|
|
692
839
|
}
|
|
693
840
|
if (diff.onlyRemote.length > 0) {
|
|
694
841
|
p3.note(
|
|
695
|
-
diff.onlyRemote.map((slug) => `${
|
|
842
|
+
diff.onlyRemote.map((slug) => `${pc4.red("-")} ${slug}`).join("\n"),
|
|
696
843
|
"Only in Remote"
|
|
697
844
|
);
|
|
698
845
|
}
|
|
@@ -700,7 +847,7 @@ function printDiff(diff) {
|
|
|
700
847
|
let changedText = "";
|
|
701
848
|
for (const item of diff.changed) {
|
|
702
849
|
changedText += `
|
|
703
|
-
${
|
|
850
|
+
${pc4.bold(item.slug)}
|
|
704
851
|
`;
|
|
705
852
|
for (const line of item.details) {
|
|
706
853
|
changedText += ` ${line}
|
|
@@ -710,47 +857,75 @@ ${pc3.bold(item.slug)}
|
|
|
710
857
|
p3.note(changedText.trim(), "Changed Plans");
|
|
711
858
|
}
|
|
712
859
|
const summary = [
|
|
713
|
-
diff.onlyLocal.length > 0 ? `${
|
|
714
|
-
diff.onlyRemote.length > 0 ? `${
|
|
715
|
-
diff.changed.length > 0 ? `${
|
|
860
|
+
diff.onlyLocal.length > 0 ? `${pc4.green(diff.onlyLocal.length.toString())} added` : "",
|
|
861
|
+
diff.onlyRemote.length > 0 ? `${pc4.red(diff.onlyRemote.length.toString())} removed` : "",
|
|
862
|
+
diff.changed.length > 0 ? `${pc4.yellow(diff.changed.length.toString())} changed` : ""
|
|
716
863
|
].filter(Boolean).join(" ");
|
|
717
864
|
p3.log.info(summary);
|
|
718
865
|
}
|
|
719
866
|
|
|
720
867
|
// src/commands/diff.ts
|
|
721
868
|
async function runDiff(options) {
|
|
722
|
-
p4.intro(
|
|
869
|
+
p4.intro(pc5.bgYellow(pc5.black(" diff ")));
|
|
723
870
|
const fullPath = resolveConfigPath(options.config);
|
|
871
|
+
if (!fullPath) {
|
|
872
|
+
p4.log.error(pc5.red("No configuration file found."));
|
|
873
|
+
process.exit(1);
|
|
874
|
+
}
|
|
724
875
|
const apiKey = getApiKey(options.key);
|
|
725
876
|
const configSettings = await loadConfigSettings(options.config);
|
|
726
877
|
const baseUrl = getApiUrl(configSettings.apiUrl);
|
|
727
878
|
const s = p4.spinner();
|
|
728
879
|
if (options.prod) {
|
|
729
|
-
p4.log.step(
|
|
880
|
+
p4.log.step(pc5.magenta("Production Mode: Comparing both environments"));
|
|
730
881
|
const testUrl = getTestApiUrl(configSettings.environments?.test);
|
|
731
882
|
const liveUrl = getApiUrl(configSettings.environments?.live);
|
|
732
883
|
s.start("Loading local configuration...");
|
|
733
|
-
|
|
884
|
+
let owo;
|
|
885
|
+
try {
|
|
886
|
+
owo = await loadOwostackFromConfig(fullPath);
|
|
887
|
+
} catch (e) {
|
|
888
|
+
s.stop(pc5.red("Failed to load configuration"));
|
|
889
|
+
p4.log.error(pc5.red(`Error: ${e.message}`));
|
|
890
|
+
p4.log.info(
|
|
891
|
+
pc5.dim(
|
|
892
|
+
"Make sure 'owostack' is installed in your project: 'npm install owostack'"
|
|
893
|
+
)
|
|
894
|
+
);
|
|
895
|
+
process.exit(1);
|
|
896
|
+
}
|
|
734
897
|
s.stop("Configuration loaded");
|
|
735
898
|
const { buildSyncPayload } = await import("owostack").catch(() => ({
|
|
736
899
|
buildSyncPayload: null
|
|
737
900
|
}));
|
|
738
901
|
const localPayload = buildSyncPayload(owo._config.catalog);
|
|
739
|
-
p4.log.step(
|
|
902
|
+
p4.log.step(pc5.cyan(`Comparing with TEST: ${testUrl}`));
|
|
740
903
|
const testPlans = await fetchPlans({ apiKey, apiUrl: `${testUrl}/api/v1` });
|
|
741
904
|
printDiff(diffPlans(localPayload?.plans ?? [], testPlans));
|
|
742
|
-
p4.log.step(
|
|
905
|
+
p4.log.step(pc5.cyan(`Comparing with LIVE: ${liveUrl}`));
|
|
743
906
|
const livePlans = await fetchPlans({ apiKey, apiUrl: `${liveUrl}/api/v1` });
|
|
744
907
|
printDiff(diffPlans(localPayload?.plans ?? [], livePlans));
|
|
745
908
|
} else {
|
|
746
909
|
s.start("Loading local configuration...");
|
|
747
|
-
|
|
910
|
+
let owo;
|
|
911
|
+
try {
|
|
912
|
+
owo = await loadOwostackFromConfig(fullPath);
|
|
913
|
+
} catch (e) {
|
|
914
|
+
s.stop(pc5.red("Failed to load configuration"));
|
|
915
|
+
p4.log.error(pc5.red(`Error: ${e.message}`));
|
|
916
|
+
p4.log.info(
|
|
917
|
+
pc5.dim(
|
|
918
|
+
"Make sure 'owostack' is installed in your project: 'npm install owostack'"
|
|
919
|
+
)
|
|
920
|
+
);
|
|
921
|
+
process.exit(1);
|
|
922
|
+
}
|
|
748
923
|
s.stop("Configuration loaded");
|
|
749
924
|
const { buildSyncPayload } = await import("owostack").catch(() => ({
|
|
750
925
|
buildSyncPayload: null
|
|
751
926
|
}));
|
|
752
927
|
const localPayload = buildSyncPayload(owo._config.catalog);
|
|
753
|
-
s.start(`Fetching remote plans from ${
|
|
928
|
+
s.start(`Fetching remote plans from ${pc5.dim(baseUrl)}...`);
|
|
754
929
|
const remotePlans = await fetchPlans({
|
|
755
930
|
apiKey,
|
|
756
931
|
apiUrl: `${baseUrl}/api/v1`
|
|
@@ -758,34 +933,44 @@ async function runDiff(options) {
|
|
|
758
933
|
s.stop("Remote plans fetched");
|
|
759
934
|
printDiff(diffPlans(localPayload?.plans ?? [], remotePlans));
|
|
760
935
|
}
|
|
761
|
-
p4.outro(
|
|
936
|
+
p4.outro(pc5.green("Diff complete \u2728"));
|
|
762
937
|
}
|
|
763
938
|
|
|
764
939
|
// src/commands/init.ts
|
|
765
940
|
import * as p6 from "@clack/prompts";
|
|
766
|
-
import
|
|
767
|
-
import { existsSync as existsSync4 } from "fs";
|
|
941
|
+
import pc7 from "picocolors";
|
|
942
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
|
|
768
943
|
import { writeFile as writeFile3 } from "fs/promises";
|
|
944
|
+
import { join as join3, resolve as resolve3, isAbsolute as isAbsolute3, extname as extname3 } from "path";
|
|
769
945
|
|
|
770
946
|
// src/lib/connect.ts
|
|
771
947
|
import * as p5 from "@clack/prompts";
|
|
772
|
-
import
|
|
948
|
+
import pc6 from "picocolors";
|
|
773
949
|
async function initiateDeviceFlow(options) {
|
|
774
950
|
const url = `${options.apiUrl}/api/auth/cli/device`;
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
951
|
+
try {
|
|
952
|
+
const response = await fetch(url, {
|
|
953
|
+
method: "POST",
|
|
954
|
+
headers: { "Content-Type": "application/json" }
|
|
955
|
+
});
|
|
956
|
+
const data = await response.json();
|
|
957
|
+
if (!response.ok || !data?.success) {
|
|
958
|
+
const message = data?.error || data?.message || "Failed to initiate device flow";
|
|
959
|
+
throw new Error(message);
|
|
960
|
+
}
|
|
961
|
+
return {
|
|
962
|
+
deviceCode: data.deviceCode,
|
|
963
|
+
userCode: data.userCode,
|
|
964
|
+
expiresIn: data.expiresIn || 300
|
|
965
|
+
};
|
|
966
|
+
} catch (error) {
|
|
967
|
+
if (error.name === "TypeError" && error.message.includes("fetch failed")) {
|
|
968
|
+
throw new Error(
|
|
969
|
+
`Could not reach the API at ${options.apiUrl}. Please check your internet connection or ensure the API is running.`
|
|
970
|
+
);
|
|
971
|
+
}
|
|
972
|
+
throw error;
|
|
783
973
|
}
|
|
784
|
-
return {
|
|
785
|
-
deviceCode: data.deviceCode,
|
|
786
|
-
userCode: data.userCode,
|
|
787
|
-
expiresIn: data.expiresIn || 300
|
|
788
|
-
};
|
|
789
974
|
}
|
|
790
975
|
async function pollForToken(deviceCode, options, s) {
|
|
791
976
|
const startTime = Date.now();
|
|
@@ -793,22 +978,29 @@ async function pollForToken(deviceCode, options, s) {
|
|
|
793
978
|
const pollInterval = 3e3;
|
|
794
979
|
while (Date.now() - startTime < timeoutMs) {
|
|
795
980
|
const url = `${options.apiUrl}/api/auth/cli/token?deviceCode=${deviceCode}`;
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
981
|
+
try {
|
|
982
|
+
const response = await fetch(url, { method: "GET" });
|
|
983
|
+
const data = await response.json();
|
|
984
|
+
if (data?.success && data?.apiKey) {
|
|
985
|
+
return {
|
|
986
|
+
success: true,
|
|
987
|
+
apiKey: data.apiKey,
|
|
988
|
+
organizationId: data.organizationId
|
|
989
|
+
};
|
|
990
|
+
}
|
|
991
|
+
if (data?.error === "expired") {
|
|
992
|
+
throw new Error("Device code expired. Please try again.");
|
|
993
|
+
}
|
|
994
|
+
if (data?.error === "denied") {
|
|
995
|
+
throw new Error("Connection was denied by user.");
|
|
996
|
+
}
|
|
997
|
+
} catch (error) {
|
|
998
|
+
if (error.name === "TypeError" && error.message.includes("fetch failed")) {
|
|
999
|
+
} else {
|
|
1000
|
+
throw error;
|
|
1001
|
+
}
|
|
810
1002
|
}
|
|
811
|
-
await new Promise((
|
|
1003
|
+
await new Promise((resolve4) => setTimeout(resolve4, pollInterval));
|
|
812
1004
|
}
|
|
813
1005
|
throw new Error("Connection timed out. Please try again.");
|
|
814
1006
|
}
|
|
@@ -816,8 +1008,8 @@ async function executeConnectFlow(options) {
|
|
|
816
1008
|
try {
|
|
817
1009
|
const deviceCode = await initiateDeviceFlow(options);
|
|
818
1010
|
const authUrl = `${options.dashboardUrl}/cli/connect?code=${deviceCode.userCode}`;
|
|
819
|
-
p5.log.step(
|
|
820
|
-
p5.log.message(`${
|
|
1011
|
+
p5.log.step(pc6.bold("Connect to your dashboard:"));
|
|
1012
|
+
p5.log.message(`${pc6.cyan(pc6.underline(authUrl))}
|
|
821
1013
|
`);
|
|
822
1014
|
const shouldOpen = !options.noBrowser;
|
|
823
1015
|
if (shouldOpen) {
|
|
@@ -831,10 +1023,10 @@ async function executeConnectFlow(options) {
|
|
|
831
1023
|
}
|
|
832
1024
|
const s = p5.spinner();
|
|
833
1025
|
s.start(
|
|
834
|
-
`Waiting for you to approve in the dashboard (Code: ${
|
|
1026
|
+
`Waiting for you to approve in the dashboard (Code: ${pc6.bold(pc6.yellow(deviceCode.userCode))})`
|
|
835
1027
|
);
|
|
836
1028
|
const result = await pollForToken(deviceCode.deviceCode, options, s);
|
|
837
|
-
s.stop(
|
|
1029
|
+
s.stop(pc6.green("Authorization granted"));
|
|
838
1030
|
if (result.success && result.apiKey) {
|
|
839
1031
|
await saveGlobalConfig({
|
|
840
1032
|
apiKey: result.apiKey,
|
|
@@ -843,19 +1035,40 @@ async function executeConnectFlow(options) {
|
|
|
843
1035
|
return result.apiKey;
|
|
844
1036
|
}
|
|
845
1037
|
} catch (e) {
|
|
846
|
-
p5.log.error(
|
|
1038
|
+
p5.log.error(pc6.red(`\u2717 ${e.message}`));
|
|
847
1039
|
}
|
|
848
1040
|
return null;
|
|
849
1041
|
}
|
|
850
1042
|
|
|
851
1043
|
// src/commands/init.ts
|
|
1044
|
+
function getProjectInfo2() {
|
|
1045
|
+
const cwd = process.cwd();
|
|
1046
|
+
const isTs = existsSync4(join3(cwd, "tsconfig.json"));
|
|
1047
|
+
let isEsm = false;
|
|
1048
|
+
try {
|
|
1049
|
+
const pkg = JSON.parse(readFileSync3(join3(cwd, "package.json"), "utf8"));
|
|
1050
|
+
isEsm = pkg.type === "module";
|
|
1051
|
+
} catch {
|
|
1052
|
+
}
|
|
1053
|
+
return { isTs, isEsm };
|
|
1054
|
+
}
|
|
852
1055
|
async function runInit(options) {
|
|
853
|
-
p6.intro(
|
|
854
|
-
|
|
1056
|
+
p6.intro(pc7.bgYellow(pc7.black(" init ")));
|
|
1057
|
+
let targetPath = options.config;
|
|
1058
|
+
if (!targetPath) {
|
|
1059
|
+
const existing = resolveConfigPath();
|
|
1060
|
+
if (existing) {
|
|
1061
|
+
targetPath = existing;
|
|
1062
|
+
} else {
|
|
1063
|
+
const { isTs } = getProjectInfo2();
|
|
1064
|
+
targetPath = isTs ? "owo.config.ts" : "owo.config.js";
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
const fullPath = isAbsolute3(targetPath) ? targetPath : resolve3(process.cwd(), targetPath);
|
|
855
1068
|
let apiKey = getApiKey(options.key);
|
|
856
1069
|
if (!apiKey) {
|
|
857
1070
|
p6.log.warn(
|
|
858
|
-
|
|
1071
|
+
pc7.yellow("No API key found. Let's connect your account first.")
|
|
859
1072
|
);
|
|
860
1073
|
apiKey = await executeConnectFlow({
|
|
861
1074
|
apiUrl: getApiUrl(),
|
|
@@ -864,7 +1077,7 @@ async function runInit(options) {
|
|
|
864
1077
|
timeout: 300
|
|
865
1078
|
}) || "";
|
|
866
1079
|
if (!apiKey) {
|
|
867
|
-
p6.log.error(
|
|
1080
|
+
p6.log.error(pc7.red("Could not obtain API key. Initialization aborted."));
|
|
868
1081
|
process.exit(1);
|
|
869
1082
|
}
|
|
870
1083
|
}
|
|
@@ -874,7 +1087,7 @@ async function runInit(options) {
|
|
|
874
1087
|
initialValue: false
|
|
875
1088
|
});
|
|
876
1089
|
if (p6.isCancel(confirm3) || !confirm3) {
|
|
877
|
-
p6.outro(
|
|
1090
|
+
p6.outro(pc7.yellow("Initialization cancelled"));
|
|
878
1091
|
process.exit(0);
|
|
879
1092
|
}
|
|
880
1093
|
}
|
|
@@ -886,22 +1099,40 @@ async function runInit(options) {
|
|
|
886
1099
|
apiKey,
|
|
887
1100
|
`${getApiUrl()}/api/v1`
|
|
888
1101
|
);
|
|
889
|
-
const
|
|
1102
|
+
const ext = extname3(fullPath);
|
|
1103
|
+
const { isEsm } = getProjectInfo2();
|
|
1104
|
+
let format = "ts";
|
|
1105
|
+
if (ext === ".ts" || ext === ".mts" || ext === ".cts") {
|
|
1106
|
+
format = "ts";
|
|
1107
|
+
} else if (ext === ".mjs") {
|
|
1108
|
+
format = "esm";
|
|
1109
|
+
} else if (ext === ".cjs") {
|
|
1110
|
+
format = "cjs";
|
|
1111
|
+
} else if (ext === ".js") {
|
|
1112
|
+
format = isEsm ? "esm" : "cjs";
|
|
1113
|
+
}
|
|
1114
|
+
const configContent = generateConfig(
|
|
1115
|
+
plans,
|
|
1116
|
+
creditSystems,
|
|
1117
|
+
void 0,
|
|
1118
|
+
format
|
|
1119
|
+
);
|
|
890
1120
|
await writeFile3(fullPath, configContent, "utf8");
|
|
891
|
-
s.stop(
|
|
1121
|
+
s.stop(pc7.green("Configuration created"));
|
|
892
1122
|
p6.note(
|
|
893
|
-
`${
|
|
894
|
-
${
|
|
895
|
-
${
|
|
1123
|
+
`${pc7.dim("File:")} ${fullPath}
|
|
1124
|
+
${pc7.dim("Format:")} ${format.toUpperCase()}
|
|
1125
|
+
${pc7.dim("Plans:")} ${plans.length} imported
|
|
1126
|
+
${pc7.dim("Credit Systems:")} ${creditSystems.length}`,
|
|
896
1127
|
"\u2728 Project Initialized"
|
|
897
1128
|
);
|
|
898
1129
|
p6.outro(
|
|
899
|
-
|
|
900
|
-
`Next step: Run ${
|
|
1130
|
+
pc7.cyan(
|
|
1131
|
+
`Next step: Run ${pc7.bold("owostack sync")} to apply your catalog.`
|
|
901
1132
|
)
|
|
902
1133
|
);
|
|
903
1134
|
} catch (e) {
|
|
904
|
-
s.stop(
|
|
1135
|
+
s.stop(pc7.red("Initialization failed"));
|
|
905
1136
|
p6.log.error(e.message);
|
|
906
1137
|
process.exit(1);
|
|
907
1138
|
}
|
|
@@ -909,25 +1140,41 @@ ${pc6.dim("Credit Systems:")} ${creditSystems.length}`,
|
|
|
909
1140
|
|
|
910
1141
|
// src/commands/validate.ts
|
|
911
1142
|
import * as p7 from "@clack/prompts";
|
|
912
|
-
import
|
|
1143
|
+
import pc8 from "picocolors";
|
|
913
1144
|
async function runValidate(options) {
|
|
914
|
-
p7.intro(
|
|
1145
|
+
p7.intro(pc8.bgYellow(pc8.black(" validate ")));
|
|
915
1146
|
const fullPath = resolveConfigPath(options.config);
|
|
1147
|
+
if (!fullPath) {
|
|
1148
|
+
p7.log.error(pc8.red("No configuration file found."));
|
|
1149
|
+
process.exit(1);
|
|
1150
|
+
}
|
|
916
1151
|
const s = p7.spinner();
|
|
917
|
-
s.start(`Loading ${
|
|
918
|
-
|
|
1152
|
+
s.start(`Loading ${pc8.cyan(fullPath)}`);
|
|
1153
|
+
let owo;
|
|
1154
|
+
try {
|
|
1155
|
+
owo = await loadOwostackFromConfig(fullPath);
|
|
1156
|
+
} catch (e) {
|
|
1157
|
+
s.stop(pc8.red("Failed to load configuration"));
|
|
1158
|
+
p7.log.error(pc8.red(`Error: ${e.message}`));
|
|
1159
|
+
p7.log.info(
|
|
1160
|
+
pc8.dim(
|
|
1161
|
+
"Make sure 'owostack' is installed in your project: 'npm install owostack'"
|
|
1162
|
+
)
|
|
1163
|
+
);
|
|
1164
|
+
process.exit(1);
|
|
1165
|
+
}
|
|
919
1166
|
if (!owo || typeof owo.sync !== "function") {
|
|
920
|
-
s.stop(
|
|
1167
|
+
s.stop(pc8.red("Invalid configuration"));
|
|
921
1168
|
p7.log.error("Config file must export an Owostack instance.");
|
|
922
1169
|
process.exit(1);
|
|
923
1170
|
}
|
|
924
1171
|
if (!owo._config?.catalog || owo._config.catalog.length === 0) {
|
|
925
|
-
s.stop(
|
|
1172
|
+
s.stop(pc8.red("No catalog found"));
|
|
926
1173
|
p7.log.error("Config has no catalog to validate.");
|
|
927
1174
|
process.exit(1);
|
|
928
1175
|
}
|
|
929
1176
|
s.stop(
|
|
930
|
-
|
|
1177
|
+
pc8.green(`Configuration loaded (${owo._config.catalog.length} entries)`)
|
|
931
1178
|
);
|
|
932
1179
|
const { buildSyncPayload } = await import("owostack").catch(() => ({
|
|
933
1180
|
buildSyncPayload: null
|
|
@@ -938,18 +1185,18 @@ async function runValidate(options) {
|
|
|
938
1185
|
}
|
|
939
1186
|
try {
|
|
940
1187
|
const payload = buildSyncPayload(owo._config.catalog);
|
|
941
|
-
p7.log.step(
|
|
1188
|
+
p7.log.step(pc8.bold("Features"));
|
|
942
1189
|
for (const f of payload.features) {
|
|
943
|
-
p7.log.message(`${
|
|
1190
|
+
p7.log.message(`${pc8.green("\u2713")} ${f.slug} ${pc8.dim(`(${f.type})`)}`);
|
|
944
1191
|
}
|
|
945
|
-
p7.log.step(
|
|
1192
|
+
p7.log.step(pc8.bold("Plans"));
|
|
946
1193
|
for (const p_obj of payload.plans) {
|
|
947
1194
|
p7.log.message(
|
|
948
|
-
`${
|
|
1195
|
+
`${pc8.green("\u2713")} ${pc8.bold(p_obj.slug)} ${pc8.dim(`${p_obj.currency} ${p_obj.price} / ${p_obj.interval}`)}`
|
|
949
1196
|
);
|
|
950
1197
|
}
|
|
951
1198
|
if (options.prod) {
|
|
952
|
-
p7.log.step(
|
|
1199
|
+
p7.log.step(pc8.magenta("Production Mode Check"));
|
|
953
1200
|
const configSettings = await loadConfigSettings(options.config);
|
|
954
1201
|
const testUrl = getTestApiUrl(configSettings.environments?.test);
|
|
955
1202
|
const liveUrl = getApiUrl(configSettings.environments?.live);
|
|
@@ -977,7 +1224,7 @@ async function runValidate(options) {
|
|
|
977
1224
|
p7.log.error(`LIVE environment check failed: ${e.message}`);
|
|
978
1225
|
}
|
|
979
1226
|
}
|
|
980
|
-
p7.outro(
|
|
1227
|
+
p7.outro(pc8.green("Validation passed! \u2728"));
|
|
981
1228
|
} catch (e) {
|
|
982
1229
|
p7.log.error(`Validation failed: ${e.message}`);
|
|
983
1230
|
process.exit(1);
|
|
@@ -986,16 +1233,16 @@ async function runValidate(options) {
|
|
|
986
1233
|
|
|
987
1234
|
// src/commands/connect.ts
|
|
988
1235
|
import * as p8 from "@clack/prompts";
|
|
989
|
-
import
|
|
1236
|
+
import pc9 from "picocolors";
|
|
990
1237
|
import { existsSync as existsSync5 } from "fs";
|
|
991
1238
|
async function runConnect(options) {
|
|
992
|
-
p8.intro(
|
|
993
|
-
const
|
|
1239
|
+
p8.intro(pc9.bgYellow(pc9.black(" connect ")));
|
|
1240
|
+
const fullPath = resolveConfigPath();
|
|
994
1241
|
let apiUrl = getApiUrl();
|
|
995
1242
|
let dashboardUrl = getDashboardUrl();
|
|
996
1243
|
let noBrowser = options.browser === false;
|
|
997
|
-
if (existsSync5(
|
|
998
|
-
const configSettings = await loadConfigSettings(
|
|
1244
|
+
if (fullPath && existsSync5(fullPath)) {
|
|
1245
|
+
const configSettings = await loadConfigSettings(fullPath);
|
|
999
1246
|
if (configSettings.connect?.dashboardUrl) {
|
|
1000
1247
|
dashboardUrl = getDashboardUrl(configSettings.connect.dashboardUrl);
|
|
1001
1248
|
}
|
|
@@ -1011,26 +1258,26 @@ async function runConnect(options) {
|
|
|
1011
1258
|
});
|
|
1012
1259
|
if (apiKey) {
|
|
1013
1260
|
p8.note(
|
|
1014
|
-
`${
|
|
1015
|
-
${
|
|
1261
|
+
`${pc9.dim("API Key:")} owo_***
|
|
1262
|
+
${pc9.dim("Config:")} ${GLOBAL_CONFIG_PATH}`,
|
|
1016
1263
|
"Connected successfully!"
|
|
1017
1264
|
);
|
|
1018
|
-
p8.outro(
|
|
1265
|
+
p8.outro(pc9.green("Authentication complete \u2728"));
|
|
1019
1266
|
} else {
|
|
1020
|
-
p8.log.error(
|
|
1267
|
+
p8.log.error(pc9.red("Connection failed. Please try again."));
|
|
1021
1268
|
process.exit(1);
|
|
1022
1269
|
}
|
|
1023
1270
|
}
|
|
1024
1271
|
|
|
1025
1272
|
// src/lib/brand.ts
|
|
1026
|
-
import
|
|
1273
|
+
import pc10 from "picocolors";
|
|
1027
1274
|
var OWO_ASCII = `
|
|
1028
|
-
${
|
|
1029
|
-
${
|
|
1030
|
-
${
|
|
1031
|
-
${
|
|
1032
|
-
${
|
|
1033
|
-
${
|
|
1275
|
+
${pc10.yellow("\u2588\u2588\u2588\u2588\u2588\u2588\u2557")} ${pc10.white("\u2588\u2588\u2557 \u2588\u2588\u2557")} ${pc10.yellow("\u2588\u2588\u2588\u2588\u2588\u2588\u2557")}
|
|
1276
|
+
${pc10.yellow("\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557")} ${pc10.white("\u2588\u2588\u2551 \u2588\u2588\u2551")} ${pc10.yellow("\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557")}
|
|
1277
|
+
${pc10.yellow("\u2588\u2588\u2551 \u2588\u2588\u2551")} ${pc10.white("\u2588\u2588\u2551 \u2588\u2557 \u2588\u2588\u2551")} ${pc10.yellow("\u2588\u2588\u2551 \u2588\u2588\u2551")}
|
|
1278
|
+
${pc10.yellow("\u2588\u2588\u2551 \u2588\u2588\u2551")} ${pc10.white("\u2588\u2588\u2551\u2588\u2588\u2588\u2557\u2588\u2588\u2551")} ${pc10.yellow("\u2588\u2588\u2551 \u2588\u2588\u2551")}
|
|
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")}
|
|
1034
1281
|
`;
|
|
1035
1282
|
function printBrand() {
|
|
1036
1283
|
console.log(OWO_ASCII);
|
|
@@ -1040,10 +1287,10 @@ function printBrand() {
|
|
|
1040
1287
|
var program = new Command();
|
|
1041
1288
|
printBrand();
|
|
1042
1289
|
program.name("owostack").description("CLI for Owostack billing infrastructure").version("0.1.0");
|
|
1043
|
-
program.command("sync").description("Push catalog to the API").option("--config <path>", "Path to config file"
|
|
1044
|
-
program.command("pull").description("Pull plans from dashboard into owo.config.ts").option("--config <path>", "Path to config file"
|
|
1045
|
-
program.command("diff").description("Compare local config to dashboard plans").option("--config <path>", "Path to config file"
|
|
1046
|
-
program.command("init").description("Initialize owo.config.ts from dashboard").option("--config <path>", "Path to config file"
|
|
1047
|
-
program.command("validate").description("Validate local config without syncing").option("--config <path>", "Path to config file"
|
|
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("--prod", "Execute in both test and live environments").option("--dry-run", "Show what would change without applying").action(runSync);
|
|
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("--prod", "Execute in both test and live environments").option("--dry-run", "Show what would change without applying").action(runPull);
|
|
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").option("--prod", "Execute in both test and live environments").action(runDiff);
|
|
1293
|
+
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").option("--prod", "Execute in both test and live environments").action(runValidate);
|
|
1048
1295
|
program.command("connect").description("Connect CLI to dashboard via browser").option("--no-browser", "Don't open the browser automatically").action(runConnect);
|
|
1049
1296
|
program.parse();
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "owosk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "CLI for Owostack - sync catalog, manage billing infrastructure",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
|
-
"url": "https://github.com/Abdulmumin1/owostack
|
|
8
|
+
"url": "https://github.com/Abdulmumin1/owostack",
|
|
9
9
|
"directory": "packages/cli"
|
|
10
10
|
},
|
|
11
11
|
"type": "module",
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"dist"
|
|
14
14
|
],
|
|
15
15
|
"bin": {
|
|
16
|
+
"owosk": "./dist/index.js",
|
|
16
17
|
"owostack": "./dist/index.js",
|
|
17
18
|
"owo": "./dist/index.js",
|
|
18
19
|
"owos": "./dist/index.js",
|
|
@@ -23,8 +24,8 @@
|
|
|
23
24
|
"commander": "^14.0.3",
|
|
24
25
|
"jiti": "^2.6.1",
|
|
25
26
|
"picocolors": "^1.1.1",
|
|
26
|
-
"owostack": "0.1.
|
|
27
|
-
"
|
|
27
|
+
"@owostack/types": "0.1.4",
|
|
28
|
+
"owostack": "0.1.4"
|
|
28
29
|
},
|
|
29
30
|
"devDependencies": {
|
|
30
31
|
"@types/node": "^22.19.10",
|