@blankdotpage/cli 0.1.1 → 0.1.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.
Files changed (2) hide show
  1. package/dist/index.js +92 -26
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3,7 +3,6 @@
3
3
  // src/index.ts
4
4
  import fs2 from "node:fs/promises";
5
5
  import os2 from "node:os";
6
- import { spawn } from "node:child_process";
7
6
  import { URL } from "node:url";
8
7
 
9
8
  // src/lib/args.ts
@@ -41,10 +40,67 @@ function booleanFlag(flags, key) {
41
40
  return flags[key] === true;
42
41
  }
43
42
 
43
+ // src/lib/browser.ts
44
+ import {
45
+ spawn
46
+ } from "node:child_process";
47
+ function getLaunchCommand(platform, url) {
48
+ if (platform === "darwin") {
49
+ return { command: "open", args: [url] };
50
+ }
51
+ if (platform === "linux") {
52
+ return { command: "xdg-open", args: [url] };
53
+ }
54
+ if (platform === "win32") {
55
+ return { command: "cmd", args: ["/c", "start", "", url] };
56
+ }
57
+ return null;
58
+ }
59
+ function toError(error) {
60
+ return error instanceof Error ? error : new Error(String(error));
61
+ }
62
+ function openBrowser(url, options = {}) {
63
+ const platform = options.platform ?? process.platform;
64
+ const launch = getLaunchCommand(platform, url);
65
+ if (!launch) {
66
+ return false;
67
+ }
68
+ const spawnCommand = options.spawnCommand ?? ((command, args, spawnOptions) => spawn(command, args, spawnOptions));
69
+ const onError = options.onError ?? (() => {
70
+ });
71
+ try {
72
+ const child = spawnCommand(launch.command, launch.args, {
73
+ stdio: "ignore",
74
+ detached: true
75
+ });
76
+ child.once("error", (error) => {
77
+ onError(toError(error));
78
+ });
79
+ child.unref();
80
+ return true;
81
+ } catch (error) {
82
+ onError(toError(error));
83
+ return false;
84
+ }
85
+ }
86
+
44
87
  // src/lib/config.ts
45
88
  import os from "node:os";
46
89
  import path from "node:path";
47
90
  import fs from "node:fs/promises";
91
+ function isObject(value) {
92
+ return typeof value === "object" && value !== null;
93
+ }
94
+ function readOptionalString(input, key) {
95
+ const value = input[key];
96
+ if (value === void 0) {
97
+ return void 0;
98
+ }
99
+ if (typeof value !== "string") {
100
+ throw new Error(`Invalid CLI config value for "${key}"`);
101
+ }
102
+ return value;
103
+ }
48
104
  function resolveConfigPath() {
49
105
  if (process.env.BLANKPAGE_CLI_CONFIG_PATH) {
50
106
  return process.env.BLANKPAGE_CLI_CONFIG_PATH;
@@ -55,15 +111,39 @@ async function loadConfig() {
55
111
  const configPath = resolveConfigPath();
56
112
  try {
57
113
  const raw = await fs.readFile(configPath, "utf8");
58
- return JSON.parse(raw);
59
- } catch {
60
- return {};
114
+ let parsed;
115
+ try {
116
+ parsed = JSON.parse(raw);
117
+ } catch (error) {
118
+ throw new Error(
119
+ `Failed to parse CLI config file at ${configPath}: ${error instanceof Error ? error.message : String(error)}`
120
+ );
121
+ }
122
+ if (!isObject(parsed)) {
123
+ throw new Error(
124
+ `Invalid CLI config file at ${configPath}: expected an object`
125
+ );
126
+ }
127
+ return {
128
+ appUrl: readOptionalString(parsed, "appUrl"),
129
+ token: readOptionalString(parsed, "token"),
130
+ tokenExpiresAt: readOptionalString(parsed, "tokenExpiresAt")
131
+ };
132
+ } catch (error) {
133
+ if (error.code === "ENOENT") {
134
+ return {};
135
+ }
136
+ throw error;
61
137
  }
62
138
  }
63
139
  async function saveConfig(config) {
64
140
  const configPath = resolveConfigPath();
65
- await fs.mkdir(path.dirname(configPath), { recursive: true });
66
- await fs.writeFile(configPath, JSON.stringify(config, null, 2), "utf8");
141
+ await fs.mkdir(path.dirname(configPath), { recursive: true, mode: 448 });
142
+ await fs.writeFile(configPath, JSON.stringify(config, null, 2), {
143
+ encoding: "utf8",
144
+ mode: 384
145
+ });
146
+ await fs.chmod(configPath, 384);
67
147
  }
68
148
 
69
149
  // src/lib/http.ts
@@ -212,23 +292,6 @@ function requireToken(token) {
212
292
  function sleep(ms) {
213
293
  return new Promise((resolve) => setTimeout(resolve, ms));
214
294
  }
215
- function maybeOpenBrowser(url) {
216
- const platform = process.platform;
217
- if (platform === "darwin") {
218
- spawn("open", [url], { stdio: "ignore", detached: true }).unref();
219
- return;
220
- }
221
- if (platform === "linux") {
222
- spawn("xdg-open", [url], { stdio: "ignore", detached: true }).unref();
223
- return;
224
- }
225
- if (platform === "win32") {
226
- spawn("cmd", ["/c", "start", "", url], {
227
- stdio: "ignore",
228
- detached: true
229
- }).unref();
230
- }
231
- }
232
295
  function output(json, payload, text) {
233
296
  if (json) {
234
297
  printJson(payload);
@@ -265,13 +328,16 @@ async function commandLogin(ctx) {
265
328
  {
266
329
  event: "login_started",
267
330
  authorizeUrl: started.authorizeUrl,
268
- challengeId: started.challengeId,
269
- challengeSecret: started.challengeSecret
331
+ challengeId: started.challengeId
270
332
  },
271
333
  `Open this URL to authorize: ${started.authorizeUrl}`
272
334
  );
273
335
  if (!noBrowser) {
274
- maybeOpenBrowser(started.authorizeUrl);
336
+ openBrowser(started.authorizeUrl, {
337
+ onError: (error) => {
338
+ printError(`Could not open browser automatically: ${error.message}`);
339
+ }
340
+ });
275
341
  }
276
342
  const deadline = Date.now() + timeoutSeconds * 1e3;
277
343
  while (Date.now() < deadline) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blankdotpage/cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "CLI for Blank Page",
5
5
  "type": "module",
6
6
  "files": [