solvapay 1.0.1-preview.2
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/LICENSE.md +21 -0
- package/README.md +19 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +573 -0
- package/package.json +48 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 SolvaPay Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# solvapay
|
|
2
|
+
|
|
3
|
+
SolvaPay CLI.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Run the SolvaPay CLI init flow
|
|
9
|
+
npx solvapay init
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## What `solvapay init` does
|
|
13
|
+
|
|
14
|
+
- Checks for `package.json` (and can create one if missing)
|
|
15
|
+
- Opens browser authentication and waits for approval
|
|
16
|
+
- Writes `SOLVAPAY_SECRET_KEY` to `.env` (with overwrite confirmation)
|
|
17
|
+
- Ensures `.env` is ignored in `.gitignore`
|
|
18
|
+
- Installs `@solvapay/server` and `@solvapay/core`
|
|
19
|
+
- Verifies the key and prints a quick-start snippet
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,573 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/lib/browser-auth.ts
|
|
4
|
+
import open from "open";
|
|
5
|
+
var INIT_EXCHANGE_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
6
|
+
var POLL_INTERVAL_MS = 2e3;
|
|
7
|
+
var sleep = async (ms) => new Promise((resolve) => {
|
|
8
|
+
setTimeout(resolve, ms);
|
|
9
|
+
});
|
|
10
|
+
var startSpinner = (message) => {
|
|
11
|
+
const frames = ["|", "/", "-", "\\"];
|
|
12
|
+
let frameIndex = 0;
|
|
13
|
+
process.stdout.write(`${message} ${frames[frameIndex]}`);
|
|
14
|
+
const timer = setInterval(() => {
|
|
15
|
+
frameIndex = (frameIndex + 1) % frames.length;
|
|
16
|
+
process.stdout.write(`\r${message} ${frames[frameIndex]}`);
|
|
17
|
+
}, 100);
|
|
18
|
+
return {
|
|
19
|
+
stop: () => {
|
|
20
|
+
clearInterval(timer);
|
|
21
|
+
process.stdout.write(`\r${" ".repeat(message.length + 4)}\r`);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
var createInitSession = async (apiBaseUrl) => {
|
|
26
|
+
const response = await fetch(`${apiBaseUrl}/v1/ui/provider/auth/cli-init/sessions`, {
|
|
27
|
+
method: "POST",
|
|
28
|
+
headers: {
|
|
29
|
+
"Content-Type": "application/json"
|
|
30
|
+
},
|
|
31
|
+
body: JSON.stringify({})
|
|
32
|
+
});
|
|
33
|
+
if (!response.ok) {
|
|
34
|
+
const body = await response.text();
|
|
35
|
+
throw new Error(`Failed to start init session (${response.status}): ${body}`);
|
|
36
|
+
}
|
|
37
|
+
return await response.json();
|
|
38
|
+
};
|
|
39
|
+
var openAuthUrl = async (authUrl) => {
|
|
40
|
+
try {
|
|
41
|
+
await open(authUrl);
|
|
42
|
+
return true;
|
|
43
|
+
} catch {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
var waitForExchange = async (apiBaseUrl, session) => {
|
|
48
|
+
const startedAt = Date.now();
|
|
49
|
+
const spinner = startSpinner("\u23F3 Waiting for authentication...");
|
|
50
|
+
try {
|
|
51
|
+
while (Date.now() - startedAt < INIT_EXCHANGE_TIMEOUT_MS) {
|
|
52
|
+
const response = await fetch(
|
|
53
|
+
`${apiBaseUrl}/v1/ui/provider/auth/cli-init/sessions/${encodeURIComponent(session.sessionId)}/exchange`,
|
|
54
|
+
{
|
|
55
|
+
method: "POST",
|
|
56
|
+
headers: {
|
|
57
|
+
Authorization: `Bearer ${session.pollToken}`
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
);
|
|
61
|
+
if (response.status === 202 || response.status === 409) {
|
|
62
|
+
await sleep(POLL_INTERVAL_MS);
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (!response.ok) {
|
|
66
|
+
const body = await response.text();
|
|
67
|
+
throw new Error(`Init exchange failed (${response.status}): ${body}`);
|
|
68
|
+
}
|
|
69
|
+
const payload = await response.json();
|
|
70
|
+
if (payload.status === "pending") {
|
|
71
|
+
await sleep(POLL_INTERVAL_MS);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
return payload;
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
status: "expired"
|
|
78
|
+
};
|
|
79
|
+
} finally {
|
|
80
|
+
spinner.stop();
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
var verifySecretKey = async (apiBaseUrl, secretKey) => {
|
|
84
|
+
try {
|
|
85
|
+
const response = await fetch(`${apiBaseUrl}/v1/sdk/products`, {
|
|
86
|
+
method: "GET",
|
|
87
|
+
headers: {
|
|
88
|
+
Authorization: `Bearer ${secretKey}`
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
if (response.ok) {
|
|
92
|
+
return { ok: true };
|
|
93
|
+
}
|
|
94
|
+
const body = await response.text();
|
|
95
|
+
return {
|
|
96
|
+
ok: false,
|
|
97
|
+
warning: `Verification failed (${response.status}): ${body}`
|
|
98
|
+
};
|
|
99
|
+
} catch (error) {
|
|
100
|
+
const message = error instanceof Error ? error.message : "Network error";
|
|
101
|
+
return {
|
|
102
|
+
ok: false,
|
|
103
|
+
warning: `Verification failed due to network error: ${message}`
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// src/commands/init.ts
|
|
109
|
+
import chalk from "chalk";
|
|
110
|
+
|
|
111
|
+
// src/lib/env.ts
|
|
112
|
+
import { access, readFile, writeFile } from "fs/promises";
|
|
113
|
+
import { constants } from "fs";
|
|
114
|
+
import path from "path";
|
|
115
|
+
import readline from "readline/promises";
|
|
116
|
+
import { stdin, stdout } from "process";
|
|
117
|
+
var SOLVAPAY_SECRET_KEY = "SOLVAPAY_SECRET_KEY";
|
|
118
|
+
var envKeyRegex = new RegExp(`^\\s*${SOLVAPAY_SECRET_KEY}\\s*=`, "m");
|
|
119
|
+
var normalizeTrailingNewline = (content) => content.endsWith("\n") ? content : `${content}
|
|
120
|
+
`;
|
|
121
|
+
var askOverwrite = async () => {
|
|
122
|
+
const rl = readline.createInterface({ input: stdin, output: stdout });
|
|
123
|
+
try {
|
|
124
|
+
const answer = (await rl.question(
|
|
125
|
+
"You're already set up. Overwrite SOLVAPAY_SECRET_KEY? (y/N) "
|
|
126
|
+
)).trim().toLowerCase();
|
|
127
|
+
return answer === "y" || answer === "yes";
|
|
128
|
+
} finally {
|
|
129
|
+
rl.close();
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
var hasEnvGitignoreEntry = (content) => content.split("\n").map((line) => line.trim()).some((line) => line === ".env" || line === "/.env");
|
|
133
|
+
var ensureEnvInGitignore = async (cwd = process.cwd()) => {
|
|
134
|
+
const gitignorePath = path.join(cwd, ".gitignore");
|
|
135
|
+
const exists = await envFileExists(gitignorePath);
|
|
136
|
+
if (!exists) {
|
|
137
|
+
await writeFile(gitignorePath, ".env\n", "utf8");
|
|
138
|
+
return { filePath: gitignorePath, action: "created" };
|
|
139
|
+
}
|
|
140
|
+
const currentContent = await readFile(gitignorePath, "utf8");
|
|
141
|
+
if (hasEnvGitignoreEntry(currentContent)) {
|
|
142
|
+
return { filePath: gitignorePath, action: "unchanged" };
|
|
143
|
+
}
|
|
144
|
+
const next = `${normalizeTrailingNewline(currentContent)}.env
|
|
145
|
+
`;
|
|
146
|
+
await writeFile(gitignorePath, next, "utf8");
|
|
147
|
+
return { filePath: gitignorePath, action: "appended" };
|
|
148
|
+
};
|
|
149
|
+
var envFileExists = async (envPath) => {
|
|
150
|
+
try {
|
|
151
|
+
await access(envPath, constants.F_OK);
|
|
152
|
+
return true;
|
|
153
|
+
} catch {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
var writeSolvaPaySecretToEnv = async (secretKey, options = {}) => {
|
|
158
|
+
const envPath = path.join(options.cwd || process.cwd(), ".env");
|
|
159
|
+
const keyLine = `${SOLVAPAY_SECRET_KEY}=${secretKey}`;
|
|
160
|
+
const exists = await envFileExists(envPath);
|
|
161
|
+
if (!exists) {
|
|
162
|
+
await writeFile(envPath, `${keyLine}
|
|
163
|
+
`, "utf8");
|
|
164
|
+
return { filePath: envPath, action: "created" };
|
|
165
|
+
}
|
|
166
|
+
const currentContent = await readFile(envPath, "utf8");
|
|
167
|
+
if (!envKeyRegex.test(currentContent)) {
|
|
168
|
+
const next = `${normalizeTrailingNewline(currentContent)}${keyLine}
|
|
169
|
+
`;
|
|
170
|
+
await writeFile(envPath, next, "utf8");
|
|
171
|
+
return { filePath: envPath, action: "appended" };
|
|
172
|
+
}
|
|
173
|
+
const shouldOverwrite = options.confirmOverwrite ? await options.confirmOverwrite() : await askOverwrite();
|
|
174
|
+
if (!shouldOverwrite) {
|
|
175
|
+
return { filePath: envPath, action: "unchanged" };
|
|
176
|
+
}
|
|
177
|
+
const updatedContent = currentContent.replace(
|
|
178
|
+
/^\s*SOLVAPAY_SECRET_KEY\s*=.*$/m,
|
|
179
|
+
keyLine
|
|
180
|
+
);
|
|
181
|
+
await writeFile(envPath, updatedContent, "utf8");
|
|
182
|
+
return { filePath: envPath, action: "updated" };
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
// src/lib/install.ts
|
|
186
|
+
import { spawn } from "child_process";
|
|
187
|
+
var MAX_ERROR_LOG_LINES = 30;
|
|
188
|
+
var DEFAULT_DIST_TAG = "latest";
|
|
189
|
+
var SOLVAPAY_BASE_PACKAGES = ["@solvapay/server", "@solvapay/core", "@solvapay/auth"];
|
|
190
|
+
var getSolvaPayBasePackages = () => [...SOLVAPAY_BASE_PACKAGES];
|
|
191
|
+
var getPackageSpecifiers = async () => {
|
|
192
|
+
const distTag = DEFAULT_DIST_TAG;
|
|
193
|
+
return SOLVAPAY_BASE_PACKAGES.map((pkg) => `${pkg}@${distTag}`);
|
|
194
|
+
};
|
|
195
|
+
var getInstallCommand = async (packageManager) => {
|
|
196
|
+
const packageSpecifiers = await getPackageSpecifiers();
|
|
197
|
+
const packageArgs = packageSpecifiers.join(" ");
|
|
198
|
+
if (packageManager === "yarn") {
|
|
199
|
+
return `yarn add ${packageArgs}`;
|
|
200
|
+
}
|
|
201
|
+
if (packageManager === "pnpm") {
|
|
202
|
+
return `pnpm add ${packageArgs}`;
|
|
203
|
+
}
|
|
204
|
+
return `npm install ${packageArgs}`;
|
|
205
|
+
};
|
|
206
|
+
var getInstallArgs = (packageManager, packageSpecifiers) => {
|
|
207
|
+
if (packageManager === "yarn") {
|
|
208
|
+
return ["add", ...packageSpecifiers];
|
|
209
|
+
}
|
|
210
|
+
if (packageManager === "pnpm") {
|
|
211
|
+
return ["add", ...packageSpecifiers];
|
|
212
|
+
}
|
|
213
|
+
return ["install", ...packageSpecifiers];
|
|
214
|
+
};
|
|
215
|
+
var ANSI_ESCAPE_RE = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
|
|
216
|
+
var trimLine = (line) => line.replace(ANSI_ESCAPE_RE, "").trim();
|
|
217
|
+
var parsePnpmProgress = (line) => {
|
|
218
|
+
const progressMatch = line.match(/Progress:\s*resolved\s+(\d+).+downloaded\s+(\d+).+added\s+(\d+)/);
|
|
219
|
+
if (progressMatch) {
|
|
220
|
+
const [, resolved, downloaded, added] = progressMatch;
|
|
221
|
+
return `Installing packages (${added} added, ${downloaded} downloaded, ${resolved} resolved)`;
|
|
222
|
+
}
|
|
223
|
+
if (line.includes("Progress:")) {
|
|
224
|
+
return "Installing packages";
|
|
225
|
+
}
|
|
226
|
+
if (line.includes("Done in")) {
|
|
227
|
+
return "Finalizing install";
|
|
228
|
+
}
|
|
229
|
+
return null;
|
|
230
|
+
};
|
|
231
|
+
var parseYarnProgress = (line) => {
|
|
232
|
+
if (/\[\d+\/\d+\]\s+Resolving packages/.test(line) || line.includes("Resolution step")) {
|
|
233
|
+
return "Resolving packages";
|
|
234
|
+
}
|
|
235
|
+
if (/\[\d+\/\d+\]\s+Fetching packages/.test(line) || line.includes("Fetch step")) {
|
|
236
|
+
return "Downloading packages";
|
|
237
|
+
}
|
|
238
|
+
if (/\[\d+\/\d+\]\s+Linking dependencies/.test(line) || line.includes("Link step")) {
|
|
239
|
+
return "Linking dependencies";
|
|
240
|
+
}
|
|
241
|
+
if (/\[\d+\/\d+\]\s+Building fresh packages/.test(line) || line.includes("Building packages")) {
|
|
242
|
+
return "Building packages";
|
|
243
|
+
}
|
|
244
|
+
if (line.includes("Done in") || line.toLowerCase().includes("done with warnings")) {
|
|
245
|
+
return "Finalizing install";
|
|
246
|
+
}
|
|
247
|
+
return null;
|
|
248
|
+
};
|
|
249
|
+
var parseNpmProgress = (line) => {
|
|
250
|
+
const lower = line.toLowerCase();
|
|
251
|
+
if (lower.includes("idealtree")) {
|
|
252
|
+
return "Resolving packages";
|
|
253
|
+
}
|
|
254
|
+
if (lower.includes("reify")) {
|
|
255
|
+
return "Installing packages";
|
|
256
|
+
}
|
|
257
|
+
if (lower.startsWith("added ") || lower.includes("audited ")) {
|
|
258
|
+
return "Finalizing install";
|
|
259
|
+
}
|
|
260
|
+
return null;
|
|
261
|
+
};
|
|
262
|
+
var getProgressMessage = (packageManager, rawLine) => {
|
|
263
|
+
const line = trimLine(rawLine);
|
|
264
|
+
if (!line) {
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
if (packageManager === "pnpm") {
|
|
268
|
+
return parsePnpmProgress(line);
|
|
269
|
+
}
|
|
270
|
+
if (packageManager === "yarn") {
|
|
271
|
+
return parseYarnProgress(line);
|
|
272
|
+
}
|
|
273
|
+
return parseNpmProgress(line);
|
|
274
|
+
};
|
|
275
|
+
var installSolvaPaySdk = async (packageManager, cwd = process.cwd(), onProgress) => {
|
|
276
|
+
const packageSpecifiers = await getPackageSpecifiers();
|
|
277
|
+
const command = await getInstallCommand(packageManager);
|
|
278
|
+
const errorLines = [];
|
|
279
|
+
let lastProgressMessage = "";
|
|
280
|
+
return new Promise((resolve) => {
|
|
281
|
+
const child = spawn(packageManager, getInstallArgs(packageManager, packageSpecifiers), {
|
|
282
|
+
cwd,
|
|
283
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
284
|
+
shell: process.platform === "win32"
|
|
285
|
+
});
|
|
286
|
+
const recordLine = (line) => {
|
|
287
|
+
const trimmed = trimLine(line);
|
|
288
|
+
if (!trimmed) {
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
errorLines.push(trimmed);
|
|
292
|
+
if (errorLines.length > MAX_ERROR_LOG_LINES) {
|
|
293
|
+
errorLines.shift();
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
const emitProgress = (line) => {
|
|
297
|
+
const message = getProgressMessage(packageManager, line);
|
|
298
|
+
if (!message || message === lastProgressMessage) {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
lastProgressMessage = message;
|
|
302
|
+
onProgress?.(message);
|
|
303
|
+
};
|
|
304
|
+
const handleChunk = (chunk) => {
|
|
305
|
+
const text = chunk.toString("utf8");
|
|
306
|
+
const lines = text.split(/\r?\n|\r/g);
|
|
307
|
+
for (const line of lines) {
|
|
308
|
+
recordLine(line);
|
|
309
|
+
emitProgress(line);
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
child.stdout?.on("data", handleChunk);
|
|
313
|
+
child.stderr?.on("data", handleChunk);
|
|
314
|
+
child.once("error", (error) => {
|
|
315
|
+
resolve({
|
|
316
|
+
ok: false,
|
|
317
|
+
command,
|
|
318
|
+
warning: error.message
|
|
319
|
+
});
|
|
320
|
+
});
|
|
321
|
+
child.once("close", (code) => {
|
|
322
|
+
if (code === 0) {
|
|
323
|
+
resolve({ ok: true, command });
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
resolve({
|
|
327
|
+
ok: false,
|
|
328
|
+
command,
|
|
329
|
+
warning: `Installer exited with code ${code ?? "unknown"}${errorLines.length ? `
|
|
330
|
+
${errorLines.join("\n")}` : ""}`
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
// src/lib/project.ts
|
|
337
|
+
import { access as access2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
338
|
+
import { constants as constants2 } from "fs";
|
|
339
|
+
import path2 from "path";
|
|
340
|
+
import readline2 from "readline/promises";
|
|
341
|
+
import { stdin as stdin2, stdout as stdout2 } from "process";
|
|
342
|
+
var fileExists = async (filePath) => {
|
|
343
|
+
try {
|
|
344
|
+
await access2(filePath, constants2.F_OK);
|
|
345
|
+
return true;
|
|
346
|
+
} catch {
|
|
347
|
+
return false;
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
var toPackageName = (cwd) => {
|
|
351
|
+
const baseName = path2.basename(cwd);
|
|
352
|
+
const sanitized = baseName.toLowerCase().replace(/[^a-z0-9._-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
353
|
+
return sanitized || "solvapay-app";
|
|
354
|
+
};
|
|
355
|
+
var askCreatePackageJson = async () => {
|
|
356
|
+
const rl = readline2.createInterface({ input: stdin2, output: stdout2 });
|
|
357
|
+
try {
|
|
358
|
+
const answer = (await rl.question("No package.json found. Create one now? (y/N) ")).trim().toLowerCase();
|
|
359
|
+
return answer === "y" || answer === "yes";
|
|
360
|
+
} finally {
|
|
361
|
+
rl.close();
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
var ensureNodeProject = async (options = {}) => {
|
|
365
|
+
const cwd = options.cwd || process.cwd();
|
|
366
|
+
const packageJsonPath = path2.join(cwd, "package.json");
|
|
367
|
+
if (await fileExists(packageJsonPath)) {
|
|
368
|
+
return { filePath: packageJsonPath, action: "existing" };
|
|
369
|
+
}
|
|
370
|
+
const shouldCreate = options.confirmCreate ? await options.confirmCreate() : await askCreatePackageJson();
|
|
371
|
+
if (!shouldCreate) {
|
|
372
|
+
return { filePath: packageJsonPath, action: "cancelled" };
|
|
373
|
+
}
|
|
374
|
+
const packageJson = {
|
|
375
|
+
name: toPackageName(cwd),
|
|
376
|
+
version: "1.0.0",
|
|
377
|
+
private: true
|
|
378
|
+
};
|
|
379
|
+
await writeFile2(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}
|
|
380
|
+
`, "utf8");
|
|
381
|
+
return { filePath: packageJsonPath, action: "created" };
|
|
382
|
+
};
|
|
383
|
+
var detectPackageManager = async (cwd = process.cwd()) => {
|
|
384
|
+
const lockfiles = [
|
|
385
|
+
{ file: "pnpm-lock.yaml", packageManager: "pnpm" },
|
|
386
|
+
{ file: "yarn.lock", packageManager: "yarn" },
|
|
387
|
+
{ file: "package-lock.json", packageManager: "npm" },
|
|
388
|
+
{ file: "npm-shrinkwrap.json", packageManager: "npm" }
|
|
389
|
+
];
|
|
390
|
+
let currentDir = path2.resolve(cwd);
|
|
391
|
+
while (true) {
|
|
392
|
+
for (const lockfile of lockfiles) {
|
|
393
|
+
if (await fileExists(path2.join(currentDir, lockfile.file))) {
|
|
394
|
+
return lockfile.packageManager;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
const packageJsonPath = path2.join(currentDir, "package.json");
|
|
398
|
+
if (await fileExists(packageJsonPath)) {
|
|
399
|
+
try {
|
|
400
|
+
const packageJson = JSON.parse(await readFile2(packageJsonPath, "utf8"));
|
|
401
|
+
if (packageJson.packageManager?.startsWith("pnpm@")) return "pnpm";
|
|
402
|
+
if (packageJson.packageManager?.startsWith("yarn@")) return "yarn";
|
|
403
|
+
if (packageJson.packageManager?.startsWith("npm@")) return "npm";
|
|
404
|
+
} catch {
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
const parentDir = path2.dirname(currentDir);
|
|
408
|
+
if (parentDir === currentDir) {
|
|
409
|
+
break;
|
|
410
|
+
}
|
|
411
|
+
currentDir = parentDir;
|
|
412
|
+
}
|
|
413
|
+
return "npm";
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
// src/commands/init.ts
|
|
417
|
+
var DEFAULT_API_BASE_URL = "https://api.solvapay.com";
|
|
418
|
+
var resolveApiBaseUrl = () => (process.env.SOLVAPAY_API_BASE_URL || DEFAULT_API_BASE_URL).replace(/\/$/, "");
|
|
419
|
+
var ASCII_BANNER = ` ____ _ ____
|
|
420
|
+
/ ___| ___ | |_ ____ _| _ \\ __ _ _ _
|
|
421
|
+
\\___ \\ / _ \\| \\ \\ / / _\` | |_) / _\` | | | |
|
|
422
|
+
___) | (_) | |\\ V / (_| | __/ (_| | |_| |
|
|
423
|
+
|____/ \\___/|_| \\_/ \\__,_|_| \\__,_|\\__, |
|
|
424
|
+
|___/`;
|
|
425
|
+
var printBanner = () => {
|
|
426
|
+
process.stdout.write(`${chalk.cyanBright(ASCII_BANNER)}
|
|
427
|
+
|
|
428
|
+
`);
|
|
429
|
+
};
|
|
430
|
+
var printQuickStart = () => {
|
|
431
|
+
process.stdout.write(`
|
|
432
|
+
You're all set! Here's how to get started:
|
|
433
|
+
|
|
434
|
+
import { SolvaPay } from '@solvapay/server';
|
|
435
|
+
const sp = new SolvaPay();
|
|
436
|
+
|
|
437
|
+
Docs: https://docs.solvapay.com
|
|
438
|
+
`);
|
|
439
|
+
};
|
|
440
|
+
var createInstallProgressReporter = () => {
|
|
441
|
+
if (!process.stdout.isTTY) {
|
|
442
|
+
return (message) => {
|
|
443
|
+
process.stdout.write(`\u{1F4E6} ${message}
|
|
444
|
+
`);
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
let lastRenderedLength = 0;
|
|
448
|
+
return (message) => {
|
|
449
|
+
const line = `\u{1F4E6} ${message}`;
|
|
450
|
+
const paddedLine = line.length < lastRenderedLength ? `${line}${" ".repeat(lastRenderedLength - line.length)}` : line;
|
|
451
|
+
process.stdout.write(`\r${paddedLine}`);
|
|
452
|
+
lastRenderedLength = line.length;
|
|
453
|
+
};
|
|
454
|
+
};
|
|
455
|
+
var finishInstallProgressReporter = () => {
|
|
456
|
+
if (process.stdout.isTTY) {
|
|
457
|
+
process.stdout.write("\n");
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
var runInitCommand = async () => {
|
|
461
|
+
const apiBaseUrl = resolveApiBaseUrl();
|
|
462
|
+
const cwd = process.cwd();
|
|
463
|
+
printBanner();
|
|
464
|
+
const projectCheck = await ensureNodeProject();
|
|
465
|
+
if (projectCheck.action === "cancelled") {
|
|
466
|
+
process.stdout.write("Initialization cancelled. Run `npm init -y` first, then `solvapay init`.\n");
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
const packageManager = await detectPackageManager(cwd);
|
|
470
|
+
if (projectCheck.action === "created") {
|
|
471
|
+
process.stdout.write(`\u{1F50D} Detected ${packageManager} project (package.json created)
|
|
472
|
+
`);
|
|
473
|
+
} else {
|
|
474
|
+
process.stdout.write(`\u{1F50D} Detected ${packageManager} project (package.json found)
|
|
475
|
+
`);
|
|
476
|
+
}
|
|
477
|
+
process.stdout.write("\u{1F310} Opening browser for authentication...\n");
|
|
478
|
+
const initSession = await createInitSession(apiBaseUrl);
|
|
479
|
+
const opened = await openAuthUrl(initSession.authUrl);
|
|
480
|
+
if (!opened) {
|
|
481
|
+
process.stdout.write(` If it doesn't open, visit: ${initSession.authUrl}
|
|
482
|
+
`);
|
|
483
|
+
}
|
|
484
|
+
const exchange = await waitForExchange(apiBaseUrl, initSession);
|
|
485
|
+
if (exchange.status === "cancelled") {
|
|
486
|
+
throw new Error("Authentication was cancelled. Run `solvapay init` again when you are ready.");
|
|
487
|
+
}
|
|
488
|
+
if (exchange.status === "expired") {
|
|
489
|
+
throw new Error(
|
|
490
|
+
"Timed out after 10 minutes waiting for authentication. Run `solvapay init` again."
|
|
491
|
+
);
|
|
492
|
+
}
|
|
493
|
+
if (exchange.status !== "complete" || !exchange.secretKey) {
|
|
494
|
+
throw new Error("Could not retrieve a SolvaPay secret key from the init session.");
|
|
495
|
+
}
|
|
496
|
+
if (exchange.email) {
|
|
497
|
+
process.stdout.write(`\u2705 Authenticated as ${exchange.email}
|
|
498
|
+
`);
|
|
499
|
+
} else {
|
|
500
|
+
process.stdout.write("\u2705 Authenticated\n");
|
|
501
|
+
}
|
|
502
|
+
const envWrite = await writeSolvaPaySecretToEnv(exchange.secretKey);
|
|
503
|
+
if (envWrite.action === "created" || envWrite.action === "appended" || envWrite.action === "updated") {
|
|
504
|
+
process.stdout.write("\u{1F4DD} Secret key saved to .env\n");
|
|
505
|
+
} else {
|
|
506
|
+
process.stdout.write("\u{1F4DD} Kept existing SOLVAPAY_SECRET_KEY in .env\n");
|
|
507
|
+
}
|
|
508
|
+
const gitignoreWrite = await ensureEnvInGitignore(cwd);
|
|
509
|
+
if (gitignoreWrite.action === "created" || gitignoreWrite.action === "appended") {
|
|
510
|
+
process.stdout.write("\u{1F512} Added .env to .gitignore\n");
|
|
511
|
+
}
|
|
512
|
+
const onInstallProgress = createInstallProgressReporter();
|
|
513
|
+
onInstallProgress("Resolving packages");
|
|
514
|
+
const installResult = await installSolvaPaySdk(packageManager, cwd, onInstallProgress);
|
|
515
|
+
finishInstallProgressReporter();
|
|
516
|
+
if (installResult.ok) {
|
|
517
|
+
process.stdout.write("\u2705 SolvaPay SDK packages installed\n");
|
|
518
|
+
const installedPackages = getSolvaPayBasePackages();
|
|
519
|
+
const packageList = installedPackages.join(", ");
|
|
520
|
+
process.stdout.write(`\u{1F4E6} Added ${installedPackages.length} packages: ${packageList}
|
|
521
|
+
`);
|
|
522
|
+
} else {
|
|
523
|
+
const manualInstallCommand = await getInstallCommand(packageManager);
|
|
524
|
+
process.stdout.write(
|
|
525
|
+
`\u26A0\uFE0F Install failed (${installResult.warning || "unknown error"}). Run manually: ${manualInstallCommand}
|
|
526
|
+
`
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
const verified = await verifySecretKey(apiBaseUrl, exchange.secretKey);
|
|
530
|
+
if (verified.ok) {
|
|
531
|
+
process.stdout.write("\u2705 Secret key verified with SolvaPay\n");
|
|
532
|
+
} else {
|
|
533
|
+
process.stdout.write(
|
|
534
|
+
`\u26A0\uFE0F Verification failed, but setup can still continue. Details: ${verified.warning}
|
|
535
|
+
`
|
|
536
|
+
);
|
|
537
|
+
}
|
|
538
|
+
process.stdout.write("\n");
|
|
539
|
+
printQuickStart();
|
|
540
|
+
};
|
|
541
|
+
|
|
542
|
+
// src/cli.ts
|
|
543
|
+
var HELP_TEXT = `SolvaPay CLI
|
|
544
|
+
|
|
545
|
+
Usage:
|
|
546
|
+
solvapay <command>
|
|
547
|
+
|
|
548
|
+
Commands:
|
|
549
|
+
init Authenticate, configure .env, and install SolvaPay SDK packages
|
|
550
|
+
`;
|
|
551
|
+
var main = async () => {
|
|
552
|
+
const command = process.argv[2];
|
|
553
|
+
if (!command || command === "--help" || command === "-h") {
|
|
554
|
+
process.stdout.write(`${HELP_TEXT}
|
|
555
|
+
`);
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
if (command === "init") {
|
|
559
|
+
await runInitCommand();
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
process.stderr.write(`Unknown command: ${command}
|
|
563
|
+
|
|
564
|
+
${HELP_TEXT}
|
|
565
|
+
`);
|
|
566
|
+
process.exitCode = 1;
|
|
567
|
+
};
|
|
568
|
+
main().catch((error) => {
|
|
569
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
570
|
+
process.stderr.write(`Error: ${message}
|
|
571
|
+
`);
|
|
572
|
+
process.exitCode = 1;
|
|
573
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "solvapay",
|
|
3
|
+
"version": "1.0.1-preview.2",
|
|
4
|
+
"description": "SolvaPay CLI",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"solvapay": "./dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "https://github.com/solvapay/solvapay-sdk.git",
|
|
19
|
+
"directory": "packages/cli"
|
|
20
|
+
},
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=18.17"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"chalk": "^5.6.2",
|
|
26
|
+
"commander": "^14.0.3",
|
|
27
|
+
"detect-package-manager": "^3.0.2",
|
|
28
|
+
"fs-extra": "^11.3.4",
|
|
29
|
+
"inquirer": "^13.3.2",
|
|
30
|
+
"open": "^11.0.0",
|
|
31
|
+
"ora": "^9.3.0",
|
|
32
|
+
"@solvapay/auth": "^1.0.1-preview.2",
|
|
33
|
+
"@solvapay/core": "^1.0.1-preview.2",
|
|
34
|
+
"@solvapay/server": "^1.0.1-preview.2"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"tsup": "^8.5.1",
|
|
38
|
+
"typescript": "^5.9.3",
|
|
39
|
+
"vitest": "^4.1.2"
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"build": "tsup --config tsup.config.ts",
|
|
43
|
+
"test": "vitest run",
|
|
44
|
+
"test:watch": "vitest",
|
|
45
|
+
"lint": "eslint src",
|
|
46
|
+
"lint:fix": "eslint src --fix"
|
|
47
|
+
}
|
|
48
|
+
}
|