@drawcall/create 0.2.0 → 0.2.1

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/shell.js +34 -6
  2. package/package.json +1 -1
package/dist/shell.js CHANGED
@@ -1,5 +1,4 @@
1
1
  import { spawn } from "node:child_process";
2
- import { createInterface } from "node:readline";
3
2
  import { delimiter, dirname, join, resolve } from "node:path";
4
3
  import which from "which";
5
4
  import { Context, Effect, Layer } from "effect";
@@ -64,16 +63,43 @@ const live = (env, logger) => ({
64
63
  env: buildSubprocessEnv(cwd, env),
65
64
  stdio: ["ignore", "pipe", "pipe"]
66
65
  });
67
- // Read both streams a line at a time so a timestamp only ever prefixes a whole line, and keep
68
- // every line for classification.
66
+ const onLine = (line) => {
67
+ captured.push(line);
68
+ Effect.runSync(logger.captured(line));
69
+ };
70
+ // Capture per stream, collapsing carriage-return redraws to their final frame: a harness
71
+ // progress spinner ("⠧ Processing 16m") rewrites one line with \r thousands of times, which
72
+ // would otherwise flood the log and bury real events. Emitting only on \n (a \r resets the
73
+ // line buffer) keeps each line whole and turns a slow step into a visible time gap between
74
+ // log lines instead of spinner spam. Real output lines are unaffected.
75
+ const flushers = [];
69
76
  for (const stream of [child.stdout, child.stderr]) {
70
77
  if (!stream)
71
78
  continue;
72
- createInterface({ input: stream }).on("line", (line) => {
73
- captured.push(line);
74
- Effect.runSync(logger.captured(line));
79
+ let pending = "";
80
+ stream.setEncoding("utf8");
81
+ stream.on("data", (chunk) => {
82
+ for (const ch of chunk) {
83
+ if (ch === "\n") {
84
+ onLine(pending);
85
+ pending = "";
86
+ }
87
+ else if (ch === "\r") {
88
+ pending = "";
89
+ }
90
+ else {
91
+ pending += ch;
92
+ }
93
+ }
94
+ });
95
+ flushers.push(() => {
96
+ if (pending.length > 0) {
97
+ onLine(pending);
98
+ pending = "";
99
+ }
75
100
  });
76
101
  }
102
+ const flushStreams = () => flushers.forEach((flush) => flush());
77
103
  let timedOut = false;
78
104
  let killTimer;
79
105
  const timeout = timeoutMs === undefined
@@ -92,6 +118,7 @@ const live = (env, logger) => ({
92
118
  };
93
119
  child.once("error", (error) => {
94
120
  cleanup();
121
+ flushStreams();
95
122
  // spawn failed (command not found, etc.): surface as a non-zero exit with the error text so
96
123
  // the caller can classify it like any other failure.
97
124
  captured.push(String(error.message ?? error));
@@ -99,6 +126,7 @@ const live = (env, logger) => ({
99
126
  });
100
127
  child.once("exit", (code, signal) => {
101
128
  cleanup();
129
+ flushStreams();
102
130
  const exitCode = timedOut ? TIMEOUT_EXIT_CODE : signal ? 1 : (code ?? 1);
103
131
  const elapsed = Math.round((Date.now() - startedAt) / 1000);
104
132
  const failure = !timedOut && exitCode !== 0 ? ` — exit ${exitCode}` : "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drawcall/create",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "type": "module",
5
5
  "description": "Create projects with an installed local harness.",
6
6
  "license": "MIT",