@doikayt/typescript-build-config 0.1.17 → 0.1.19

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/README.md CHANGED
@@ -142,8 +142,9 @@ npm install # fetch the declared devD
142
142
 
143
143
  `init` writes the canonical scripts (`ci`, `build`, `test`, `update-all-format`,
144
144
  `check-all-format`, and their sub-tasks), declares `vitest` +
145
- `@doikayt/autogen-markdown-doc` + `typescript`, and seeds a commented
146
- `vitest.config.ts`. Answering the "publishable library?" prompt sets the publish
145
+ `@doikayt/autogen-markdown-doc` + `typescript` + `@changesets/cli` (the release
146
+ pipeline needs it), and seeds a commented `vitest.config.ts`. Answering the
147
+ "publishable library?" prompt sets the publish
147
148
  config — `private` for an app, or `prepack` + the `dist/`-based fields for a
148
149
  library (see [The canonical script set](#the-canonical-script-set)).
149
150
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@doikayt/typescript-build-config",
3
- "version": "0.1.17",
3
+ "version": "0.1.19",
4
4
  "description": "Shared build configuration for TypeScript projects — ESLint, Prettier, tsconfig, and Playwright presets",
5
5
  "_comments": {
6
6
  "exports": "This block lists the config files that will be extended by files in in src/toplevel - which are copied to consuming project root folder. An example: eslint.config.js imports from '@doikayt/typescript-build-config/eslint'.",
@@ -31,7 +31,16 @@ export function canonicalScripts({ ui = false, library = false } = {}) {
31
31
  }
32
32
 
33
33
  export function devDependencyNames({ ui = false } = {}) {
34
- const names = ["vitest", "@doikayt/autogen-markdown-doc", "typescript"];
34
+ // @changesets/cli is required by the release pipeline (release.yml runs
35
+ // `npx changeset version` / `publish`), so every consumer needs it installed —
36
+ // otherwise `npx changeset` can't resolve the `changeset` bin and the release
37
+ // job fails with "could not determine executable to run".
38
+ const names = [
39
+ "vitest",
40
+ "@doikayt/autogen-markdown-doc",
41
+ "typescript",
42
+ "@changesets/cli",
43
+ ];
35
44
  if (ui) names.push("@playwright/test");
36
45
  return names;
37
46
  }
package/src/init.js CHANGED
@@ -42,38 +42,71 @@ function dirIsEmpty(dir) {
42
42
  return !existsSync(dir) || readdirSync(dir).length === 0;
43
43
  }
44
44
 
45
- // Default interactive prompt. Injectable so runInit is testable without stdin.
46
- async function askYesNo(question) {
45
+ // Default interactive prompt. Buffers input lines as they arrive and lets each
46
+ // question pull the next one, so it works with both a live terminal and piped /
47
+ // non-interactive stdin (scripts, CI). `rl.question` alone drops lines that
48
+ // arrive before the next call — with a pipe every line lands at once, which made
49
+ // init silently no-op. On EOF, unanswered prompts default to No. Injectable, so
50
+ // runInit stays testable without stdin.
51
+ function createAskYesNo() {
47
52
  const rl = createInterface({ input: process.stdin, output: process.stdout });
48
- try {
49
- const answer = await new Promise((res) => rl.question(question, res));
50
- return /^y(es)?$/i.test(answer.trim());
51
- } finally {
52
- rl.close();
53
- }
53
+ const lines = []; // input lines not yet consumed by a prompt
54
+ const waiters = []; // prompt resolvers waiting for the next line
55
+ let closed = false;
56
+ rl.on("line", (line) => {
57
+ const waiter = waiters.shift();
58
+ if (waiter) waiter(line);
59
+ else lines.push(line);
60
+ });
61
+ rl.on("close", () => {
62
+ closed = true;
63
+ while (waiters.length) waiters.shift()("");
64
+ });
65
+ const ask = (question) =>
66
+ new Promise((res) => {
67
+ process.stdout.write(question);
68
+ const answer = (line) => res(/^y(es)?$/i.test(line.trim()));
69
+ if (lines.length) answer(lines.shift());
70
+ else if (closed) answer("");
71
+ else waiters.push(answer);
72
+ });
73
+ ask.close = () => rl.close();
74
+ return ask;
54
75
  }
55
76
 
56
77
  export async function runInit({
57
78
  cwd = process.cwd(),
58
- prompt = askYesNo,
79
+ prompt,
59
80
  resolveDevVersions = resolveVersions,
60
81
  log = console.log,
61
82
  } = {}) {
83
+ // One readline interface for all prompts (see createAskYesNo); close it even
84
+ // on error so a leftover interface never keeps the process alive.
85
+ const ownAsk = prompt ? null : createAskYesNo();
86
+ const ask = prompt ?? ownAsk;
87
+ try {
88
+ return await collectAndScaffold({ cwd, ask, resolveDevVersions, log });
89
+ } finally {
90
+ ownAsk?.close();
91
+ }
92
+ }
93
+
94
+ async function collectAndScaffold({ cwd, ask, resolveDevVersions, log }) {
62
95
  const pkgPath = resolve(cwd, "package.json");
63
96
  const raw = readFileSync(pkgPath, "utf8");
64
97
 
65
98
  // Collect every answer up front, before doing any work or printing next
66
99
  // steps, so the flow reads: ask everything → act → report.
67
- const ui = await prompt(
100
+ const ui = await ask(
68
101
  "Is this a UI project that needs Playwright (e2e)? Default is No. [y/N] ",
69
102
  );
70
103
 
71
- const library = await prompt(
72
- "Is this a publishable library (published to npm)? Default is No — an\n" +
73
- "app/CLI, marked private so it is never published. [y/N] ",
104
+ const library = await ask(
105
+ "Is this a publishable library (published to npm)? Default is No\n" +
106
+ " — a non-library (an app/CLI) is marked private so it is never published. [y/N] ",
74
107
  );
75
108
 
76
- const wantDemo = await prompt(
109
+ const wantDemo = await ask(
77
110
  "Scaffold a starter demo (minimal src module + README + project.json) so\n" +
78
111
  "you can build, test, and release immediately? Default is No; existing\n" +
79
112
  "files are never overwritten. [y/N] ",