@sandro-sikic/maker 1.0.6 → 1.0.8

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/example.js ADDED
@@ -0,0 +1,13 @@
1
+ import * as maker from './index.js';
2
+
3
+ (async () => {
4
+ maker.init();
5
+
6
+ const spinner = await maker.spinner('Testing maker.run()...').start();
7
+
8
+ await new Promise((resolve) => setTimeout(resolve, 1000));
9
+
10
+ await maker.run('echo "Hello, World!"');
11
+
12
+ spinner.succeed('Done!');
13
+ })();
package/index.d.ts CHANGED
@@ -16,13 +16,8 @@ export function run(
16
16
 
17
17
  export function onExit(cb: () => void | Promise<void>): () => void;
18
18
 
19
- // runtime-forwarded objects (exported as values in JS)
20
- export const prompt: <T = any>(...args: any[]) => Promise<T | null>;
21
- export const spinner: (
22
- text?: string,
23
- options?: any,
24
- ) => Promise<import('ora').Ora | null>;
19
+ export const prompt: typeof import('@inquirer/prompts');
20
+ export const spinner: typeof import('ora');
25
21
 
26
- // type forwarding for downstream consumers
27
22
  export type { Ora } from 'ora';
28
23
  export * from '@inquirer/prompts';
package/index.js CHANGED
@@ -1,37 +1,11 @@
1
- const { spawn } = require('child_process');
2
- const path = require('path');
3
-
4
- // small helper to lazily load (CJS-first, ESM dynamic import fallback)
5
- function makeLazyLoader(moduleId) {
6
- let _cached = null;
7
- return async function load() {
8
- if (_cached) return _cached;
9
- try {
10
- /* eslint-disable global-require, import/no-dynamic-require */
11
- const mod = require(moduleId);
12
- _cached = mod && (mod.default || mod);
13
- return _cached;
14
- } catch (err) {
15
- try {
16
- const ns = await import(moduleId);
17
- _cached = ns && (ns.default || ns);
18
- return _cached;
19
- } catch (e) {
20
- // unable to load in this environment — return null so callers can continue
21
- return null;
22
- }
23
- }
24
- };
25
- }
26
-
27
- const loadPrompts = makeLazyLoader('@inquirer/prompts');
28
- const loadOra = makeLazyLoader('ora');
1
+ import * as inquirer from '@inquirer/prompts';
2
+ import * as ora from 'ora';
29
3
 
30
4
  function init() {
31
5
  // Ensure we're running in an interactive terminal
32
6
  if (!process.stdin.isTTY || !process.stdout.isTTY) {
33
7
  console.error(
34
- '\nThis TUI requires an interactive terminal. Run this in a terminal (not the Node REPL or a non-interactive/debug console).\n',
8
+ '\nThis TUI requires an interactive terminal. Run this in a terminal (not the Node REPL or a non-interactive/debug console).\n',
35
9
  );
36
10
  process.exit(1);
37
11
  }
@@ -49,21 +23,28 @@ function init() {
49
23
  * @param {number} [opts.maxLines=10000] - Maximum number of lines to retain in captured output.
50
24
  * @returns {Promise<{output:string, stdout:string, stderr:string, code:number|null, isError:boolean, error:Error|null}>}
51
25
  */
52
- function run(command, opts = {}) {
26
+ async function run(command, opts = {}) {
27
+ if (typeof command !== 'string' || !command.trim()) {
28
+ throw new TypeError('run() requires a non-empty string command');
29
+ }
30
+
53
31
  const { maxLines = 10000 } = opts;
54
- const normalized = path.normalize(command);
55
- const split = normalized.split('/');
56
- const convertedCommand = path.join(...split);
32
+ // import spawn at runtime so test mocks (vi.mock) take effect per-test
33
+ const { spawn } = await import('child_process');
57
34
 
58
35
  function trimToLastNLines(s, n) {
59
36
  if (!s) return s;
60
- const lines = s.split(/\r?\n/);
61
- if (lines.length <= n) return s;
62
- return lines.slice(-n).join('\n');
37
+ // remove trailing newlines for accurate line counting
38
+ const hadTrailingNewline = /(?:\r?\n)$/.test(s);
39
+ const trimmed = s.replace(/(?:\r?\n)+$/g, '');
40
+ const lines = trimmed.split(/\r?\n/);
41
+ if (lines.length <= n) return hadTrailingNewline ? trimmed + '\n' : trimmed;
42
+ const out = lines.slice(-n).join('\n');
43
+ return hadTrailingNewline ? out + '\n' : out;
63
44
  }
64
45
 
65
46
  return new Promise((resolve) => {
66
- const child = spawn(convertedCommand, { shell: true, stdio: 'pipe' });
47
+ const child = spawn(command, { shell: true, stdio: 'pipe' });
67
48
 
68
49
  let stdout = '';
69
50
  let stderr = '';
@@ -118,34 +99,19 @@ function run(command, opts = {}) {
118
99
  });
119
100
  }
120
101
 
121
- /**
122
- * Register a callback to run when the process receives SIGINT (Ctrl+C).
123
- * - Accepts a function (may be async).
124
- * - Returns a disposer function that removes the listener.
125
- *
126
- * Note: spinner is started asynchronously (ora is lazy-loaded); onExit itself
127
- * remains synchronous so callers can register the handler immediately.
128
- */
129
102
  function onExit(cb) {
130
103
  if (typeof cb !== 'function') {
131
104
  throw new TypeError('onExit requires a callback function');
132
105
  }
133
106
 
134
107
  let called = false;
135
- let exiting = null;
136
-
137
- // start spinner asynchronously (best-effort; ignore load errors)
138
- loadOra().then((o) => {
139
- try {
140
- exiting = o('Gracefully shutting down...').start();
141
- } catch (e) {
142
- /* ignore */
143
- }
144
- });
145
108
 
146
109
  const handler = async () => {
147
110
  if (called) return;
148
111
  called = true;
112
+
113
+ const exiting = spinner('Gracefully shutting down...').start();
114
+
149
115
  try {
150
116
  await Promise.resolve(cb());
151
117
  } catch (err) {
@@ -159,28 +125,17 @@ function onExit(cb) {
159
125
  };
160
126
 
161
127
  process.on('SIGINT', handler);
128
+ process.on('SIGTERM', handler);
129
+ process.on('SIGQUIT', handler);
162
130
 
163
- // return a function that removes the listener in case the caller wants to cancel
164
- return () => process.off('SIGINT', handler);
165
- }
166
-
167
- // `prompt` is an async wrapper around the ESM-only `@inquirer/prompts` package.
168
- // `prompt(...args)` forwards the call to the underlying module and returns its result.
169
- async function prompt(...args) {
170
- const p = await loadPrompts();
171
- if (!p) return null; // prompts unavailable in this environment
172
- return p(...args);
131
+ return () => {
132
+ process.off('SIGINT', handler);
133
+ process.off('SIGTERM', handler);
134
+ process.off('SIGQUIT', handler);
135
+ };
173
136
  }
174
137
 
175
- async function spinner(...args) {
176
- const s = await loadOra();
177
- return s(...args);
178
- }
138
+ const prompt = inquirer.default ?? inquirer;
139
+ const spinner = ora.default ?? ora;
179
140
 
180
- module.exports = {
181
- init,
182
- run,
183
- onExit,
184
- prompt,
185
- spinner,
186
- };
141
+ export { init, run, onExit, prompt, spinner };
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "@sandro-sikic/maker",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
+ "type": "module",
4
5
  "description": "A library to create dev cli",
5
6
  "main": "index.js",
6
7
  "types": "index.d.ts",
7
8
  "scripts": {
8
- "test": "jest"
9
+ "test": "vitest"
9
10
  },
10
11
  "author": "sandro-sikic",
11
12
  "repository": {
@@ -18,12 +19,6 @@
18
19
  "ora": "^9.3.0"
19
20
  },
20
21
  "devDependencies": {
21
- "archiver": "^5.3.1",
22
- "pkg": "^5.8.1",
23
- "rimraf": "^5.0.1",
24
- "jest": "^29.7.0",
25
- "babel-jest": "^29.7.0",
26
- "@babel/core": "^7.23.0",
27
- "@babel/preset-env": "^7.23.0"
22
+ "vitest": "^4.0.18"
28
23
  }
29
24
  }