@solvapay/init 0.3.0 → 0.4.0-preview-e7613cf5668b4f45b2e72a5047d08441e78ebd41

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.cjs CHANGED
@@ -30,7 +30,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
- SOLVAPAY_PRODUCT_REF_PLACEHOLDER: () => SOLVAPAY_PRODUCT_REF_PLACEHOLDER,
33
+ SOLVAPAY_PRODUCT_REF_PLACEHOLDER: () => SOLVAPAY_PRODUCT_REF_PLACEHOLDER2,
34
34
  askKeepConfiguredProduct: () => askKeepConfiguredProduct,
35
35
  createInitSession: () => createInitSession,
36
36
  detectPackageManager: () => detectPackageManager,
@@ -43,7 +43,10 @@ __export(index_exports, {
43
43
  listProducts: () => listProducts,
44
44
  openAuthUrl: () => openAuthUrl,
45
45
  pickProductInteractive: () => pickProductInteractive,
46
+ readSolvaPayApiBaseUrlFromEnv: () => readSolvaPayApiBaseUrlFromEnv,
46
47
  readSolvaPayProductRefFromEnv: () => readSolvaPayProductRefFromEnv,
48
+ readSolvaPaySecretKeyFromEnv: () => readSolvaPaySecretKeyFromEnv,
49
+ runDoctorInDirectory: () => runDoctorInDirectory,
47
50
  runInitInDirectory: () => runInitInDirectory,
48
51
  verifyProductRef: () => verifyProductRef,
49
52
  verifySecretKey: () => verifySecretKey,
@@ -56,6 +59,7 @@ __export(index_exports, {
56
59
  module.exports = __toCommonJS(index_exports);
57
60
 
58
61
  // src/browser-auth.ts
62
+ var import_core = require("@solvapay/core");
59
63
  var import_open = __toESM(require("open"), 1);
60
64
  var INIT_EXCHANGE_TIMEOUT_MS = 10 * 60 * 1e3;
61
65
  var POLL_INTERVAL_MS = 2e3;
@@ -139,9 +143,19 @@ var waitForExchange = async (apiBaseUrl, session) => {
139
143
  spinner.stop();
140
144
  }
141
145
  };
142
- var PRODUCT_REF_PLACEHOLDER = "__SOLVAPAY_PRODUCT_REF__";
146
+ var parseVerifiedProduct = (payload) => {
147
+ if (!payload || typeof payload !== "object") return void 0;
148
+ const root = payload;
149
+ const data = root.data && typeof root.data === "object" ? root.data : root;
150
+ const name = typeof data.name === "string" ? data.name : void 0;
151
+ const status = typeof data.status === "string" ? data.status : void 0;
152
+ if (!name || !status) return void 0;
153
+ const rawPlans = Array.isArray(data.plans) ? data.plans : [];
154
+ const plans = rawPlans.filter((plan) => !!plan && typeof plan === "object").map((plan) => ({ isActive: plan.isActive === true }));
155
+ return { name, status, plans };
156
+ };
143
157
  var verifyProductRef = async (apiBaseUrl, secretKey, productRef) => {
144
- if (productRef === PRODUCT_REF_PLACEHOLDER) {
158
+ if (productRef === import_core.SOLVAPAY_PRODUCT_REF_PLACEHOLDER) {
145
159
  return { status: "invalid_placeholder" };
146
160
  }
147
161
  try {
@@ -155,7 +169,15 @@ var verifyProductRef = async (apiBaseUrl, secretKey, productRef) => {
155
169
  }
156
170
  );
157
171
  if (response.ok) {
158
- return { status: "ok" };
172
+ const payload = await response.json();
173
+ const product = parseVerifiedProduct(payload);
174
+ if (!product) {
175
+ return {
176
+ status: "error",
177
+ message: "Product lookup succeeded but the response was missing name/status."
178
+ };
179
+ }
180
+ return { status: "ok", product };
159
181
  }
160
182
  const body = await response.text();
161
183
  if (response.status === 404) {
@@ -275,10 +297,11 @@ var import_node_fs = require("fs");
275
297
  var import_node_path = __toESM(require("path"), 1);
276
298
  var import_promises2 = __toESM(require("readline/promises"), 1);
277
299
  var import_node_process = require("process");
300
+ var import_core2 = require("@solvapay/core");
278
301
  var SOLVAPAY_SECRET_KEY = "SOLVAPAY_SECRET_KEY";
279
302
  var SOLVAPAY_PRODUCT_REF = "SOLVAPAY_PRODUCT_REF";
280
303
  var SOLVAPAY_API_BASE_URL = "SOLVAPAY_API_BASE_URL";
281
- var SOLVAPAY_PRODUCT_REF_PLACEHOLDER = "__SOLVAPAY_PRODUCT_REF__";
304
+ var SOLVAPAY_PRODUCT_REF_PLACEHOLDER2 = import_core2.SOLVAPAY_PRODUCT_REF_PLACEHOLDER;
282
305
  var envValueRegex = (key) => new RegExp(`^\\s*${key}\\s*=\\s*(.*)$`, "m");
283
306
  var secretKeyRegex = envValueRegex(SOLVAPAY_SECRET_KEY);
284
307
  var parseEnvValue = (raw) => {
@@ -353,20 +376,23 @@ var writeSolvaPaySecretToEnv = async (secretKey, options = {}) => {
353
376
  await (0, import_promises.writeFile)(envPath, updatedContent, "utf8");
354
377
  return { filePath: envPath, action: "updated" };
355
378
  };
356
- var readSolvaPayProductRefFromEnv = async (cwd = process.cwd()) => {
379
+ var readEnvKeyFromFile = async (key, cwd) => {
357
380
  const envPath = import_node_path.default.join(cwd, ".env");
358
381
  const exists = await envFileExists(envPath);
359
382
  if (!exists) {
360
383
  return void 0;
361
384
  }
362
385
  const content = await (0, import_promises.readFile)(envPath, "utf8");
363
- const match = content.match(envValueRegex(SOLVAPAY_PRODUCT_REF));
386
+ const match = content.match(envValueRegex(key));
364
387
  if (!match?.[1]) {
365
388
  return void 0;
366
389
  }
367
390
  const value = parseEnvValue(match[1]);
368
391
  return value.length > 0 ? value : void 0;
369
392
  };
393
+ var readSolvaPayProductRefFromEnv = async (cwd = process.cwd()) => readEnvKeyFromFile(SOLVAPAY_PRODUCT_REF, cwd);
394
+ var readSolvaPaySecretKeyFromEnv = async (cwd = process.cwd()) => readEnvKeyFromFile(SOLVAPAY_SECRET_KEY, cwd);
395
+ var readSolvaPayApiBaseUrlFromEnv = async (cwd = process.cwd()) => readEnvKeyFromFile(SOLVAPAY_API_BASE_URL, cwd);
370
396
  var writeSolvaPayProductRefToEnv = async (productRef, options = {}) => {
371
397
  const envPath = import_node_path.default.join(options.cwd || process.cwd(), ".env");
372
398
  const keyLine = `${SOLVAPAY_PRODUCT_REF}=${productRef}`;
@@ -587,14 +613,14 @@ var parseProductSummary = (value) => {
587
613
  return null;
588
614
  }
589
615
  const record = value;
590
- if (typeof record.reference !== "string" || typeof record.name !== "string" || typeof record.status !== "string" || typeof record.createdAt !== "string") {
616
+ if (typeof record.reference !== "string" || typeof record.name !== "string") {
591
617
  return null;
592
618
  }
593
619
  return {
594
620
  reference: record.reference,
595
621
  name: record.name,
596
- status: record.status,
597
- createdAt: record.createdAt
622
+ status: typeof record.status === "string" ? record.status : "",
623
+ createdAt: typeof record.createdAt === "string" ? record.createdAt : ""
598
624
  };
599
625
  };
600
626
  var listProducts = async (apiBaseUrl, secretKey, options = {}) => {
@@ -690,7 +716,7 @@ var pickProductInteractive = async (apiBaseUrl, secretKey, options) => {
690
716
  return { action: "skipped", reason: "network_error" };
691
717
  }
692
718
  const { products, total } = listResult;
693
- if (products.length === 0) {
719
+ if (total === 0) {
694
720
  process.stdout.write(
695
721
  `\u26A0\uFE0F No products found on your SolvaPay account. Create one at ${CONSOLE_PRODUCTS_URL} and re-run \`solvapay init\`.
696
722
  `
@@ -881,7 +907,7 @@ var configureProductRef = async (apiBaseUrl, secretKey, cwd, options) => {
881
907
  return;
882
908
  }
883
909
  const existing = await readSolvaPayProductRefFromEnv(cwd);
884
- if (existing && existing !== SOLVAPAY_PRODUCT_REF_PLACEHOLDER) {
910
+ if (existing && existing !== SOLVAPAY_PRODUCT_REF_PLACEHOLDER2) {
885
911
  const verified = await verifyProductRef(apiBaseUrl, secretKey, existing);
886
912
  if (verified.status === "ok") {
887
913
  if (options.yes) {
@@ -1112,6 +1138,153 @@ var runInitInDirectory = async ({
1112
1138
  process.stdout.write("\n");
1113
1139
  printSetupComplete();
1114
1140
  };
1141
+
1142
+ // src/run-doctor.ts
1143
+ var import_chalk2 = __toESM(require("chalk"), 1);
1144
+ var import_core3 = require("@solvapay/core");
1145
+ var DEFAULT_API_BASE_URL2 = "https://api.solvapay.com";
1146
+ var DEV_API_BASE_URL2 = "https://api-dev.solvapay.com";
1147
+ var preferEnv = (processValue, fileValue) => {
1148
+ if (typeof processValue === "string" && processValue.trim().length > 0) {
1149
+ return processValue.trim();
1150
+ }
1151
+ if (typeof fileValue === "string" && fileValue.trim().length > 0) {
1152
+ return fileValue.trim();
1153
+ }
1154
+ return void 0;
1155
+ };
1156
+ var resolveApiBaseUrl2 = async (cwd, opts) => {
1157
+ if (opts.dev) return DEV_API_BASE_URL2;
1158
+ const fromFile = await readSolvaPayApiBaseUrlFromEnv(cwd);
1159
+ const resolved = preferEnv(process.env.SOLVAPAY_API_BASE_URL, fromFile);
1160
+ return (resolved || DEFAULT_API_BASE_URL2).replace(/\/$/, "");
1161
+ };
1162
+ var printCheck = (check) => {
1163
+ const mark = check.ok ? import_chalk2.default.green("\u2713") : import_chalk2.default.red("\u2717");
1164
+ process.stdout.write(`${mark} ${check.name}: ${check.detail}
1165
+ `);
1166
+ };
1167
+ var runDoctorInDirectory = async ({
1168
+ cwd,
1169
+ options = {}
1170
+ }) => {
1171
+ const checks = [];
1172
+ const apiBaseUrl = await resolveApiBaseUrl2(cwd, options);
1173
+ process.stdout.write(`
1174
+ SolvaPay doctor \u2014 ${apiBaseUrl}
1175
+
1176
+ `);
1177
+ const secretFromFile = await readSolvaPaySecretKeyFromEnv(cwd);
1178
+ const secretKey = preferEnv(process.env.SOLVAPAY_SECRET_KEY, secretFromFile);
1179
+ if (!secretKey) {
1180
+ checks.push({
1181
+ name: "SOLVAPAY_SECRET_KEY",
1182
+ ok: false,
1183
+ detail: "missing (set via `npx solvapay init` or your environment)"
1184
+ });
1185
+ } else {
1186
+ const verified = await verifySecretKey(apiBaseUrl, secretKey);
1187
+ checks.push({
1188
+ name: "SOLVAPAY_SECRET_KEY",
1189
+ ok: verified.ok,
1190
+ detail: verified.ok ? "accepted by the API" : verified.warning ?? "rejected by the API"
1191
+ });
1192
+ }
1193
+ checks.push({
1194
+ name: "SOLVAPAY_API_BASE_URL",
1195
+ ok: true,
1196
+ detail: apiBaseUrl
1197
+ });
1198
+ if (!secretKey) {
1199
+ try {
1200
+ const response = await fetch(`${apiBaseUrl}/v1/sdk/products`, { method: "GET" });
1201
+ checks.push({
1202
+ name: "API reachability",
1203
+ ok: response.status !== 0,
1204
+ detail: response.status === 401 || response.status === 403 ? `reachable (HTTP ${response.status} without a key \u2014 expected)` : `HTTP ${response.status}`
1205
+ });
1206
+ } catch (error) {
1207
+ const message = error instanceof Error ? error.message : "Network error";
1208
+ checks.push({
1209
+ name: "API reachability",
1210
+ ok: false,
1211
+ detail: message
1212
+ });
1213
+ }
1214
+ }
1215
+ const productRefFromFile = await readSolvaPayProductRefFromEnv(cwd);
1216
+ const productRef = preferEnv(process.env.SOLVAPAY_PRODUCT_REF, productRefFromFile);
1217
+ if (!productRef) {
1218
+ checks.push({
1219
+ name: "SOLVAPAY_PRODUCT_REF",
1220
+ ok: false,
1221
+ detail: "missing"
1222
+ });
1223
+ } else if (productRef === import_core3.SOLVAPAY_PRODUCT_REF_PLACEHOLDER) {
1224
+ checks.push({
1225
+ name: "SOLVAPAY_PRODUCT_REF",
1226
+ ok: false,
1227
+ detail: `still the scaffolder placeholder "${import_core3.SOLVAPAY_PRODUCT_REF_PLACEHOLDER}"`
1228
+ });
1229
+ } else if (!secretKey) {
1230
+ checks.push({
1231
+ name: "SOLVAPAY_PRODUCT_REF",
1232
+ ok: false,
1233
+ detail: `set to "${productRef}" but cannot verify without SOLVAPAY_SECRET_KEY`
1234
+ });
1235
+ } else {
1236
+ const verified = await verifyProductRef(apiBaseUrl, secretKey, productRef);
1237
+ if (verified.status === "ok") {
1238
+ const readiness = (0, import_core3.evaluateProductReadiness)(verified.product);
1239
+ checks.push({
1240
+ name: "SOLVAPAY_PRODUCT_REF",
1241
+ ok: true,
1242
+ detail: `exists \u2014 "${verified.product.name}" (${verified.product.status})`
1243
+ });
1244
+ checks.push({
1245
+ name: "Product readiness",
1246
+ ok: readiness.ready,
1247
+ detail: readiness.ready ? `${readiness.activePlans}/${readiness.totalPlans} active plan(s)` : readiness.issues.join("; ")
1248
+ });
1249
+ } else if (verified.status === "not_found") {
1250
+ checks.push({
1251
+ name: "SOLVAPAY_PRODUCT_REF",
1252
+ ok: false,
1253
+ detail: `"${productRef}" not found on ${apiBaseUrl}`
1254
+ });
1255
+ } else if (verified.status === "invalid_placeholder") {
1256
+ checks.push({
1257
+ name: "SOLVAPAY_PRODUCT_REF",
1258
+ ok: false,
1259
+ detail: `still the scaffolder placeholder`
1260
+ });
1261
+ } else {
1262
+ checks.push({
1263
+ name: "SOLVAPAY_PRODUCT_REF",
1264
+ ok: false,
1265
+ detail: verified.status === "error" ? verified.message : "could not verify"
1266
+ });
1267
+ }
1268
+ }
1269
+ const debugRaw = process.env.SOLVAPAY_DEBUG;
1270
+ checks.push({
1271
+ name: "SOLVAPAY_DEBUG defaults",
1272
+ ok: true,
1273
+ detail: debugRaw === void 0 ? 'warning: unset \u2014 factory treats debug as ON (!== "false") while client/paywall treat it as OFF (=== "true"). Set explicitly to "true" or "false".' : `set to "${debugRaw}"`
1274
+ });
1275
+ for (const check of checks) {
1276
+ printCheck(check);
1277
+ }
1278
+ const ok = checks.every((check) => check.ok);
1279
+ process.stdout.write(
1280
+ ok ? `
1281
+ ${import_chalk2.default.green("All checks passed.")}
1282
+ ` : `
1283
+ ${import_chalk2.default.red("Doctor found problems. Fix the \u2717 items above, then re-run.")}
1284
+ `
1285
+ );
1286
+ return { ok, checks, apiBaseUrl };
1287
+ };
1115
1288
  // Annotate the CommonJS export names for ESM import in node:
1116
1289
  0 && (module.exports = {
1117
1290
  SOLVAPAY_PRODUCT_REF_PLACEHOLDER,
@@ -1127,7 +1300,10 @@ var runInitInDirectory = async ({
1127
1300
  listProducts,
1128
1301
  openAuthUrl,
1129
1302
  pickProductInteractive,
1303
+ readSolvaPayApiBaseUrlFromEnv,
1130
1304
  readSolvaPayProductRefFromEnv,
1305
+ readSolvaPaySecretKeyFromEnv,
1306
+ runDoctorInDirectory,
1131
1307
  runInitInDirectory,
1132
1308
  verifyProductRef,
1133
1309
  verifySecretKey,
package/dist/index.d.cts CHANGED
@@ -13,10 +13,18 @@ type ExchangeResponse = {
13
13
  declare const createInitSession: (apiBaseUrl: string) => Promise<InitSessionResponse>;
14
14
  declare const openAuthUrl: (authUrl: string) => Promise<boolean>;
15
15
  declare const waitForExchange: (apiBaseUrl: string, session: InitSessionResponse) => Promise<ExchangeResponse>;
16
+ type VerifiedProductSummary = {
17
+ name: string;
18
+ status: string;
19
+ plans: Array<{
20
+ isActive: boolean;
21
+ }>;
22
+ };
16
23
  type VerifyProductRefResult = {
17
24
  status: 'skipped';
18
25
  } | {
19
26
  status: 'ok';
27
+ product: VerifiedProductSummary;
20
28
  } | {
21
29
  status: 'invalid_placeholder';
22
30
  } | {
@@ -48,6 +56,8 @@ type EnvWriteOptions = {
48
56
  declare const ensureEnvInGitignore: (cwd?: string) => Promise<GitignoreEnvResult>;
49
57
  declare const writeSolvaPaySecretToEnv: (secretKey: string, options?: EnvWriteOptions) => Promise<EnvWriteResult>;
50
58
  declare const readSolvaPayProductRefFromEnv: (cwd?: string) => Promise<string | undefined>;
59
+ declare const readSolvaPaySecretKeyFromEnv: (cwd?: string) => Promise<string | undefined>;
60
+ declare const readSolvaPayApiBaseUrlFromEnv: (cwd?: string) => Promise<string | undefined>;
51
61
  declare const writeSolvaPayProductRefToEnv: (productRef: string, options?: {
52
62
  cwd?: string;
53
63
  }) => Promise<EnvWriteResult>;
@@ -159,4 +169,29 @@ type RunInitInDirectoryOptions = {
159
169
  };
160
170
  declare const runInitInDirectory: ({ cwd, options, skipSdkInstall, }: RunInitInDirectoryOptions) => Promise<void>;
161
171
 
162
- export { type EnsureNodeProjectResult, type EnvWriteResult, type ExchangeResponse, type GitignoreEnvResult, type InitCommandOptions, type InitSessionResponse, type InstallResult, type ListProductsResult, type PackageManager, type PickResult, type ProductSummary, type RunInitInDirectoryOptions, SOLVAPAY_PRODUCT_REF_PLACEHOLDER, type VerifyProductRefResult, askKeepConfiguredProduct, createInitSession, detectPackageManager, ensureEnvInGitignore, ensureNodeProject, formatConfiguredProductLabel, getInstallCommand, getSolvaPayBasePackages, installSolvaPaySdk, listProducts, openAuthUrl, pickProductInteractive, readSolvaPayProductRefFromEnv, runInitInDirectory, verifyProductRef, verifySecretKey, waitForEnter, waitForExchange, writeSolvaPayApiBaseUrlToEnv, writeSolvaPayProductRefToEnv, writeSolvaPaySecretToEnv };
172
+ type DoctorCommandOptions = {
173
+ /** Target api-dev.solvapay.com (same as `solvapay init --dev`). */
174
+ dev?: boolean;
175
+ };
176
+ type RunDoctorInDirectoryOptions = {
177
+ cwd: string;
178
+ options?: DoctorCommandOptions;
179
+ };
180
+ type DoctorCheckResult = {
181
+ name: string;
182
+ ok: boolean;
183
+ detail: string;
184
+ };
185
+ type DoctorReport = {
186
+ ok: boolean;
187
+ checks: DoctorCheckResult[];
188
+ apiBaseUrl: string;
189
+ };
190
+ /**
191
+ * Diagnose SolvaPay credentials + product readiness for the current project.
192
+ * Exits non-zero (via `report.ok === false`) when any hard check fails so it
193
+ * can gate CI / pre-deploy.
194
+ */
195
+ declare const runDoctorInDirectory: ({ cwd, options, }: RunDoctorInDirectoryOptions) => Promise<DoctorReport>;
196
+
197
+ export { type DoctorCheckResult, type DoctorCommandOptions, type DoctorReport, type EnsureNodeProjectResult, type EnvWriteResult, type ExchangeResponse, type GitignoreEnvResult, type InitCommandOptions, type InitSessionResponse, type InstallResult, type ListProductsResult, type PackageManager, type PickResult, type ProductSummary, type RunDoctorInDirectoryOptions, type RunInitInDirectoryOptions, SOLVAPAY_PRODUCT_REF_PLACEHOLDER, type VerifiedProductSummary, type VerifyProductRefResult, askKeepConfiguredProduct, createInitSession, detectPackageManager, ensureEnvInGitignore, ensureNodeProject, formatConfiguredProductLabel, getInstallCommand, getSolvaPayBasePackages, installSolvaPaySdk, listProducts, openAuthUrl, pickProductInteractive, readSolvaPayApiBaseUrlFromEnv, readSolvaPayProductRefFromEnv, readSolvaPaySecretKeyFromEnv, runDoctorInDirectory, runInitInDirectory, verifyProductRef, verifySecretKey, waitForEnter, waitForExchange, writeSolvaPayApiBaseUrlToEnv, writeSolvaPayProductRefToEnv, writeSolvaPaySecretToEnv };
package/dist/index.d.ts CHANGED
@@ -13,10 +13,18 @@ type ExchangeResponse = {
13
13
  declare const createInitSession: (apiBaseUrl: string) => Promise<InitSessionResponse>;
14
14
  declare const openAuthUrl: (authUrl: string) => Promise<boolean>;
15
15
  declare const waitForExchange: (apiBaseUrl: string, session: InitSessionResponse) => Promise<ExchangeResponse>;
16
+ type VerifiedProductSummary = {
17
+ name: string;
18
+ status: string;
19
+ plans: Array<{
20
+ isActive: boolean;
21
+ }>;
22
+ };
16
23
  type VerifyProductRefResult = {
17
24
  status: 'skipped';
18
25
  } | {
19
26
  status: 'ok';
27
+ product: VerifiedProductSummary;
20
28
  } | {
21
29
  status: 'invalid_placeholder';
22
30
  } | {
@@ -48,6 +56,8 @@ type EnvWriteOptions = {
48
56
  declare const ensureEnvInGitignore: (cwd?: string) => Promise<GitignoreEnvResult>;
49
57
  declare const writeSolvaPaySecretToEnv: (secretKey: string, options?: EnvWriteOptions) => Promise<EnvWriteResult>;
50
58
  declare const readSolvaPayProductRefFromEnv: (cwd?: string) => Promise<string | undefined>;
59
+ declare const readSolvaPaySecretKeyFromEnv: (cwd?: string) => Promise<string | undefined>;
60
+ declare const readSolvaPayApiBaseUrlFromEnv: (cwd?: string) => Promise<string | undefined>;
51
61
  declare const writeSolvaPayProductRefToEnv: (productRef: string, options?: {
52
62
  cwd?: string;
53
63
  }) => Promise<EnvWriteResult>;
@@ -159,4 +169,29 @@ type RunInitInDirectoryOptions = {
159
169
  };
160
170
  declare const runInitInDirectory: ({ cwd, options, skipSdkInstall, }: RunInitInDirectoryOptions) => Promise<void>;
161
171
 
162
- export { type EnsureNodeProjectResult, type EnvWriteResult, type ExchangeResponse, type GitignoreEnvResult, type InitCommandOptions, type InitSessionResponse, type InstallResult, type ListProductsResult, type PackageManager, type PickResult, type ProductSummary, type RunInitInDirectoryOptions, SOLVAPAY_PRODUCT_REF_PLACEHOLDER, type VerifyProductRefResult, askKeepConfiguredProduct, createInitSession, detectPackageManager, ensureEnvInGitignore, ensureNodeProject, formatConfiguredProductLabel, getInstallCommand, getSolvaPayBasePackages, installSolvaPaySdk, listProducts, openAuthUrl, pickProductInteractive, readSolvaPayProductRefFromEnv, runInitInDirectory, verifyProductRef, verifySecretKey, waitForEnter, waitForExchange, writeSolvaPayApiBaseUrlToEnv, writeSolvaPayProductRefToEnv, writeSolvaPaySecretToEnv };
172
+ type DoctorCommandOptions = {
173
+ /** Target api-dev.solvapay.com (same as `solvapay init --dev`). */
174
+ dev?: boolean;
175
+ };
176
+ type RunDoctorInDirectoryOptions = {
177
+ cwd: string;
178
+ options?: DoctorCommandOptions;
179
+ };
180
+ type DoctorCheckResult = {
181
+ name: string;
182
+ ok: boolean;
183
+ detail: string;
184
+ };
185
+ type DoctorReport = {
186
+ ok: boolean;
187
+ checks: DoctorCheckResult[];
188
+ apiBaseUrl: string;
189
+ };
190
+ /**
191
+ * Diagnose SolvaPay credentials + product readiness for the current project.
192
+ * Exits non-zero (via `report.ok === false`) when any hard check fails so it
193
+ * can gate CI / pre-deploy.
194
+ */
195
+ declare const runDoctorInDirectory: ({ cwd, options, }: RunDoctorInDirectoryOptions) => Promise<DoctorReport>;
196
+
197
+ export { type DoctorCheckResult, type DoctorCommandOptions, type DoctorReport, type EnsureNodeProjectResult, type EnvWriteResult, type ExchangeResponse, type GitignoreEnvResult, type InitCommandOptions, type InitSessionResponse, type InstallResult, type ListProductsResult, type PackageManager, type PickResult, type ProductSummary, type RunDoctorInDirectoryOptions, type RunInitInDirectoryOptions, SOLVAPAY_PRODUCT_REF_PLACEHOLDER, type VerifiedProductSummary, type VerifyProductRefResult, askKeepConfiguredProduct, createInitSession, detectPackageManager, ensureEnvInGitignore, ensureNodeProject, formatConfiguredProductLabel, getInstallCommand, getSolvaPayBasePackages, installSolvaPaySdk, listProducts, openAuthUrl, pickProductInteractive, readSolvaPayApiBaseUrlFromEnv, readSolvaPayProductRefFromEnv, readSolvaPaySecretKeyFromEnv, runDoctorInDirectory, runInitInDirectory, verifyProductRef, verifySecretKey, waitForEnter, waitForExchange, writeSolvaPayApiBaseUrlToEnv, writeSolvaPayProductRefToEnv, writeSolvaPaySecretToEnv };
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  // src/browser-auth.ts
2
+ import { SOLVAPAY_PRODUCT_REF_PLACEHOLDER } from "@solvapay/core";
2
3
  import open from "open";
3
4
  var INIT_EXCHANGE_TIMEOUT_MS = 10 * 60 * 1e3;
4
5
  var POLL_INTERVAL_MS = 2e3;
@@ -82,9 +83,19 @@ var waitForExchange = async (apiBaseUrl, session) => {
82
83
  spinner.stop();
83
84
  }
84
85
  };
85
- var PRODUCT_REF_PLACEHOLDER = "__SOLVAPAY_PRODUCT_REF__";
86
+ var parseVerifiedProduct = (payload) => {
87
+ if (!payload || typeof payload !== "object") return void 0;
88
+ const root = payload;
89
+ const data = root.data && typeof root.data === "object" ? root.data : root;
90
+ const name = typeof data.name === "string" ? data.name : void 0;
91
+ const status = typeof data.status === "string" ? data.status : void 0;
92
+ if (!name || !status) return void 0;
93
+ const rawPlans = Array.isArray(data.plans) ? data.plans : [];
94
+ const plans = rawPlans.filter((plan) => !!plan && typeof plan === "object").map((plan) => ({ isActive: plan.isActive === true }));
95
+ return { name, status, plans };
96
+ };
86
97
  var verifyProductRef = async (apiBaseUrl, secretKey, productRef) => {
87
- if (productRef === PRODUCT_REF_PLACEHOLDER) {
98
+ if (productRef === SOLVAPAY_PRODUCT_REF_PLACEHOLDER) {
88
99
  return { status: "invalid_placeholder" };
89
100
  }
90
101
  try {
@@ -98,7 +109,15 @@ var verifyProductRef = async (apiBaseUrl, secretKey, productRef) => {
98
109
  }
99
110
  );
100
111
  if (response.ok) {
101
- return { status: "ok" };
112
+ const payload = await response.json();
113
+ const product = parseVerifiedProduct(payload);
114
+ if (!product) {
115
+ return {
116
+ status: "error",
117
+ message: "Product lookup succeeded but the response was missing name/status."
118
+ };
119
+ }
120
+ return { status: "ok", product };
102
121
  }
103
122
  const body = await response.text();
104
123
  if (response.status === 404) {
@@ -218,10 +237,11 @@ import { constants } from "fs";
218
237
  import path from "path";
219
238
  import readline from "readline/promises";
220
239
  import { stdin, stdout } from "process";
240
+ import { SOLVAPAY_PRODUCT_REF_PLACEHOLDER as CORE_PRODUCT_REF_PLACEHOLDER } from "@solvapay/core";
221
241
  var SOLVAPAY_SECRET_KEY = "SOLVAPAY_SECRET_KEY";
222
242
  var SOLVAPAY_PRODUCT_REF = "SOLVAPAY_PRODUCT_REF";
223
243
  var SOLVAPAY_API_BASE_URL = "SOLVAPAY_API_BASE_URL";
224
- var SOLVAPAY_PRODUCT_REF_PLACEHOLDER = "__SOLVAPAY_PRODUCT_REF__";
244
+ var SOLVAPAY_PRODUCT_REF_PLACEHOLDER2 = CORE_PRODUCT_REF_PLACEHOLDER;
225
245
  var envValueRegex = (key) => new RegExp(`^\\s*${key}\\s*=\\s*(.*)$`, "m");
226
246
  var secretKeyRegex = envValueRegex(SOLVAPAY_SECRET_KEY);
227
247
  var parseEnvValue = (raw) => {
@@ -296,20 +316,23 @@ var writeSolvaPaySecretToEnv = async (secretKey, options = {}) => {
296
316
  await writeFile(envPath, updatedContent, "utf8");
297
317
  return { filePath: envPath, action: "updated" };
298
318
  };
299
- var readSolvaPayProductRefFromEnv = async (cwd = process.cwd()) => {
319
+ var readEnvKeyFromFile = async (key, cwd) => {
300
320
  const envPath = path.join(cwd, ".env");
301
321
  const exists = await envFileExists(envPath);
302
322
  if (!exists) {
303
323
  return void 0;
304
324
  }
305
325
  const content = await readFile(envPath, "utf8");
306
- const match = content.match(envValueRegex(SOLVAPAY_PRODUCT_REF));
326
+ const match = content.match(envValueRegex(key));
307
327
  if (!match?.[1]) {
308
328
  return void 0;
309
329
  }
310
330
  const value = parseEnvValue(match[1]);
311
331
  return value.length > 0 ? value : void 0;
312
332
  };
333
+ var readSolvaPayProductRefFromEnv = async (cwd = process.cwd()) => readEnvKeyFromFile(SOLVAPAY_PRODUCT_REF, cwd);
334
+ var readSolvaPaySecretKeyFromEnv = async (cwd = process.cwd()) => readEnvKeyFromFile(SOLVAPAY_SECRET_KEY, cwd);
335
+ var readSolvaPayApiBaseUrlFromEnv = async (cwd = process.cwd()) => readEnvKeyFromFile(SOLVAPAY_API_BASE_URL, cwd);
313
336
  var writeSolvaPayProductRefToEnv = async (productRef, options = {}) => {
314
337
  const envPath = path.join(options.cwd || process.cwd(), ".env");
315
338
  const keyLine = `${SOLVAPAY_PRODUCT_REF}=${productRef}`;
@@ -530,14 +553,14 @@ var parseProductSummary = (value) => {
530
553
  return null;
531
554
  }
532
555
  const record = value;
533
- if (typeof record.reference !== "string" || typeof record.name !== "string" || typeof record.status !== "string" || typeof record.createdAt !== "string") {
556
+ if (typeof record.reference !== "string" || typeof record.name !== "string") {
534
557
  return null;
535
558
  }
536
559
  return {
537
560
  reference: record.reference,
538
561
  name: record.name,
539
- status: record.status,
540
- createdAt: record.createdAt
562
+ status: typeof record.status === "string" ? record.status : "",
563
+ createdAt: typeof record.createdAt === "string" ? record.createdAt : ""
541
564
  };
542
565
  };
543
566
  var listProducts = async (apiBaseUrl, secretKey, options = {}) => {
@@ -633,7 +656,7 @@ var pickProductInteractive = async (apiBaseUrl, secretKey, options) => {
633
656
  return { action: "skipped", reason: "network_error" };
634
657
  }
635
658
  const { products, total } = listResult;
636
- if (products.length === 0) {
659
+ if (total === 0) {
637
660
  process.stdout.write(
638
661
  `\u26A0\uFE0F No products found on your SolvaPay account. Create one at ${CONSOLE_PRODUCTS_URL} and re-run \`solvapay init\`.
639
662
  `
@@ -824,7 +847,7 @@ var configureProductRef = async (apiBaseUrl, secretKey, cwd, options) => {
824
847
  return;
825
848
  }
826
849
  const existing = await readSolvaPayProductRefFromEnv(cwd);
827
- if (existing && existing !== SOLVAPAY_PRODUCT_REF_PLACEHOLDER) {
850
+ if (existing && existing !== SOLVAPAY_PRODUCT_REF_PLACEHOLDER2) {
828
851
  const verified = await verifyProductRef(apiBaseUrl, secretKey, existing);
829
852
  if (verified.status === "ok") {
830
853
  if (options.yes) {
@@ -1055,8 +1078,158 @@ var runInitInDirectory = async ({
1055
1078
  process.stdout.write("\n");
1056
1079
  printSetupComplete();
1057
1080
  };
1081
+
1082
+ // src/run-doctor.ts
1083
+ import chalk2 from "chalk";
1084
+ import {
1085
+ evaluateProductReadiness,
1086
+ SOLVAPAY_PRODUCT_REF_PLACEHOLDER as SOLVAPAY_PRODUCT_REF_PLACEHOLDER3
1087
+ } from "@solvapay/core";
1088
+ var DEFAULT_API_BASE_URL2 = "https://api.solvapay.com";
1089
+ var DEV_API_BASE_URL2 = "https://api-dev.solvapay.com";
1090
+ var preferEnv = (processValue, fileValue) => {
1091
+ if (typeof processValue === "string" && processValue.trim().length > 0) {
1092
+ return processValue.trim();
1093
+ }
1094
+ if (typeof fileValue === "string" && fileValue.trim().length > 0) {
1095
+ return fileValue.trim();
1096
+ }
1097
+ return void 0;
1098
+ };
1099
+ var resolveApiBaseUrl2 = async (cwd, opts) => {
1100
+ if (opts.dev) return DEV_API_BASE_URL2;
1101
+ const fromFile = await readSolvaPayApiBaseUrlFromEnv(cwd);
1102
+ const resolved = preferEnv(process.env.SOLVAPAY_API_BASE_URL, fromFile);
1103
+ return (resolved || DEFAULT_API_BASE_URL2).replace(/\/$/, "");
1104
+ };
1105
+ var printCheck = (check) => {
1106
+ const mark = check.ok ? chalk2.green("\u2713") : chalk2.red("\u2717");
1107
+ process.stdout.write(`${mark} ${check.name}: ${check.detail}
1108
+ `);
1109
+ };
1110
+ var runDoctorInDirectory = async ({
1111
+ cwd,
1112
+ options = {}
1113
+ }) => {
1114
+ const checks = [];
1115
+ const apiBaseUrl = await resolveApiBaseUrl2(cwd, options);
1116
+ process.stdout.write(`
1117
+ SolvaPay doctor \u2014 ${apiBaseUrl}
1118
+
1119
+ `);
1120
+ const secretFromFile = await readSolvaPaySecretKeyFromEnv(cwd);
1121
+ const secretKey = preferEnv(process.env.SOLVAPAY_SECRET_KEY, secretFromFile);
1122
+ if (!secretKey) {
1123
+ checks.push({
1124
+ name: "SOLVAPAY_SECRET_KEY",
1125
+ ok: false,
1126
+ detail: "missing (set via `npx solvapay init` or your environment)"
1127
+ });
1128
+ } else {
1129
+ const verified = await verifySecretKey(apiBaseUrl, secretKey);
1130
+ checks.push({
1131
+ name: "SOLVAPAY_SECRET_KEY",
1132
+ ok: verified.ok,
1133
+ detail: verified.ok ? "accepted by the API" : verified.warning ?? "rejected by the API"
1134
+ });
1135
+ }
1136
+ checks.push({
1137
+ name: "SOLVAPAY_API_BASE_URL",
1138
+ ok: true,
1139
+ detail: apiBaseUrl
1140
+ });
1141
+ if (!secretKey) {
1142
+ try {
1143
+ const response = await fetch(`${apiBaseUrl}/v1/sdk/products`, { method: "GET" });
1144
+ checks.push({
1145
+ name: "API reachability",
1146
+ ok: response.status !== 0,
1147
+ detail: response.status === 401 || response.status === 403 ? `reachable (HTTP ${response.status} without a key \u2014 expected)` : `HTTP ${response.status}`
1148
+ });
1149
+ } catch (error) {
1150
+ const message = error instanceof Error ? error.message : "Network error";
1151
+ checks.push({
1152
+ name: "API reachability",
1153
+ ok: false,
1154
+ detail: message
1155
+ });
1156
+ }
1157
+ }
1158
+ const productRefFromFile = await readSolvaPayProductRefFromEnv(cwd);
1159
+ const productRef = preferEnv(process.env.SOLVAPAY_PRODUCT_REF, productRefFromFile);
1160
+ if (!productRef) {
1161
+ checks.push({
1162
+ name: "SOLVAPAY_PRODUCT_REF",
1163
+ ok: false,
1164
+ detail: "missing"
1165
+ });
1166
+ } else if (productRef === SOLVAPAY_PRODUCT_REF_PLACEHOLDER3) {
1167
+ checks.push({
1168
+ name: "SOLVAPAY_PRODUCT_REF",
1169
+ ok: false,
1170
+ detail: `still the scaffolder placeholder "${SOLVAPAY_PRODUCT_REF_PLACEHOLDER3}"`
1171
+ });
1172
+ } else if (!secretKey) {
1173
+ checks.push({
1174
+ name: "SOLVAPAY_PRODUCT_REF",
1175
+ ok: false,
1176
+ detail: `set to "${productRef}" but cannot verify without SOLVAPAY_SECRET_KEY`
1177
+ });
1178
+ } else {
1179
+ const verified = await verifyProductRef(apiBaseUrl, secretKey, productRef);
1180
+ if (verified.status === "ok") {
1181
+ const readiness = evaluateProductReadiness(verified.product);
1182
+ checks.push({
1183
+ name: "SOLVAPAY_PRODUCT_REF",
1184
+ ok: true,
1185
+ detail: `exists \u2014 "${verified.product.name}" (${verified.product.status})`
1186
+ });
1187
+ checks.push({
1188
+ name: "Product readiness",
1189
+ ok: readiness.ready,
1190
+ detail: readiness.ready ? `${readiness.activePlans}/${readiness.totalPlans} active plan(s)` : readiness.issues.join("; ")
1191
+ });
1192
+ } else if (verified.status === "not_found") {
1193
+ checks.push({
1194
+ name: "SOLVAPAY_PRODUCT_REF",
1195
+ ok: false,
1196
+ detail: `"${productRef}" not found on ${apiBaseUrl}`
1197
+ });
1198
+ } else if (verified.status === "invalid_placeholder") {
1199
+ checks.push({
1200
+ name: "SOLVAPAY_PRODUCT_REF",
1201
+ ok: false,
1202
+ detail: `still the scaffolder placeholder`
1203
+ });
1204
+ } else {
1205
+ checks.push({
1206
+ name: "SOLVAPAY_PRODUCT_REF",
1207
+ ok: false,
1208
+ detail: verified.status === "error" ? verified.message : "could not verify"
1209
+ });
1210
+ }
1211
+ }
1212
+ const debugRaw = process.env.SOLVAPAY_DEBUG;
1213
+ checks.push({
1214
+ name: "SOLVAPAY_DEBUG defaults",
1215
+ ok: true,
1216
+ detail: debugRaw === void 0 ? 'warning: unset \u2014 factory treats debug as ON (!== "false") while client/paywall treat it as OFF (=== "true"). Set explicitly to "true" or "false".' : `set to "${debugRaw}"`
1217
+ });
1218
+ for (const check of checks) {
1219
+ printCheck(check);
1220
+ }
1221
+ const ok = checks.every((check) => check.ok);
1222
+ process.stdout.write(
1223
+ ok ? `
1224
+ ${chalk2.green("All checks passed.")}
1225
+ ` : `
1226
+ ${chalk2.red("Doctor found problems. Fix the \u2717 items above, then re-run.")}
1227
+ `
1228
+ );
1229
+ return { ok, checks, apiBaseUrl };
1230
+ };
1058
1231
  export {
1059
- SOLVAPAY_PRODUCT_REF_PLACEHOLDER,
1232
+ SOLVAPAY_PRODUCT_REF_PLACEHOLDER2 as SOLVAPAY_PRODUCT_REF_PLACEHOLDER,
1060
1233
  askKeepConfiguredProduct,
1061
1234
  createInitSession,
1062
1235
  detectPackageManager,
@@ -1069,7 +1242,10 @@ export {
1069
1242
  listProducts,
1070
1243
  openAuthUrl,
1071
1244
  pickProductInteractive,
1245
+ readSolvaPayApiBaseUrlFromEnv,
1072
1246
  readSolvaPayProductRefFromEnv,
1247
+ readSolvaPaySecretKeyFromEnv,
1248
+ runDoctorInDirectory,
1073
1249
  runInitInDirectory,
1074
1250
  verifyProductRef,
1075
1251
  verifySecretKey,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solvapay/init",
3
- "version": "0.3.0",
3
+ "version": "0.4.0-preview-e7613cf5668b4f45b2e72a5047d08441e78ebd41",
4
4
  "description": "Shared primitives for SolvaPay CLI tooling (browser auth, env writers, product picker, install runner). Consumed by `solvapay` and `create-solvapay`.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -31,7 +31,8 @@
31
31
  "sideEffects": false,
32
32
  "dependencies": {
33
33
  "chalk": "^5.6.2",
34
- "open": "^11.0.0"
34
+ "open": "^11.0.0",
35
+ "@solvapay/core": "1.3.0-preview-e7613cf5668b4f45b2e72a5047d08441e78ebd41"
35
36
  },
36
37
  "devDependencies": {
37
38
  "tsup": "^8.5.1",