@doikayt/typescript-build-config 0.1.16 → 0.1.17

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@doikayt/typescript-build-config",
3
- "version": "0.1.16",
3
+ "version": "0.1.17",
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'.",
package/src/init.js CHANGED
@@ -62,13 +62,21 @@ export async function runInit({
62
62
  const pkgPath = resolve(cwd, "package.json");
63
63
  const raw = readFileSync(pkgPath, "utf8");
64
64
 
65
+ // Collect every answer up front, before doing any work or printing next
66
+ // steps, so the flow reads: ask everything → act → report.
65
67
  const ui = await prompt(
66
- "Is this a UI project that needs Playwright (e2e)? [y/N] ",
68
+ "Is this a UI project that needs Playwright (e2e)? Default is No. [y/N] ",
67
69
  );
68
70
 
69
71
  const library = await prompt(
70
- "Is this a publishable library (published to npm)? Apps/CLIs answer No and\n" +
71
- "are marked private so they are never published. [y/N] ",
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] ",
74
+ );
75
+
76
+ const wantDemo = await prompt(
77
+ "Scaffold a starter demo (minimal src module + README + project.json) so\n" +
78
+ "you can build, test, and release immediately? Default is No; existing\n" +
79
+ "files are never overwritten. [y/N] ",
72
80
  );
73
81
 
74
82
  const scripts = canonicalScripts({ ui, library });
@@ -78,6 +86,13 @@ export async function runInit({
78
86
  scripts,
79
87
  devDependencies,
80
88
  fields: packageFields({ library }),
89
+ // `npm init -y` seeds these placeholders; treat them as unset so the
90
+ // canonical `test` script and (for a library) `main` replace them instead of
91
+ // shadowing them.
92
+ replaceDefaults: {
93
+ scripts: { test: 'echo "Error: no test specified" && exit 1' },
94
+ fields: { main: "index.js" },
95
+ },
81
96
  });
82
97
  writeFileSync(pkgPath, text);
83
98
 
@@ -90,6 +105,16 @@ export async function runInit({
90
105
  (result === "seeded" ? seededConfigs : keptConfigs).push(name);
91
106
  }
92
107
 
108
+ const demo = wantDemo
109
+ ? scaffoldDemo({ cwd })
110
+ : {
111
+ scaffolded: false,
112
+ reason: "declined",
113
+ wrote: [],
114
+ skipped: [],
115
+ paths: [],
116
+ };
117
+
93
118
  const report = (label, keys) => {
94
119
  if (keys.length) log(`${label}: ${keys.join(", ")}`);
95
120
  };
@@ -101,28 +126,28 @@ export async function runInit({
101
126
  report("Kept existing package fields", skipped.fields);
102
127
  report("Seeded config files", seededConfigs);
103
128
  report("Kept existing config files", keptConfigs);
104
- log("");
105
- log("Next: run `npm install` to fetch the newly declared devDependencies.");
129
+ report("Demo seeded", demo.wrote);
130
+ report("Demo kept existing", demo.skipped);
106
131
 
107
- const demo = await maybeScaffoldDemo({ cwd, prompt, log });
132
+ log("");
133
+ if (demo.scaffolded) {
134
+ log(
135
+ "Next: `npm install`, then `npm test` and `npm run update-all-format`, " +
136
+ "then commit and push to cut a release.",
137
+ );
138
+ log(`To remove the starter files later: rm -r ${demo.paths.join(" ")}`);
139
+ } else {
140
+ log("Next: run `npm install` to fetch the newly declared devDependencies.");
141
+ }
108
142
 
109
143
  return { added, skipped, seededConfigs, keptConfigs, demo };
110
144
  }
111
145
 
112
- // Opt-in starter scaffold for a new project. Seeds a minimal src/ module, a
113
- // README with doc-generator markers, and a project.json — each independently and
114
- // only when its slot is empty, so nothing a consumer already has is overwritten.
115
- // It writes files only; the user runs npm install / test / push themselves.
116
- async function maybeScaffoldDemo({ cwd, prompt, log }) {
117
- const wantDemo = await prompt(
118
- "Scaffold a starter demo (minimal src module + README + project.json) so you\n" +
119
- "can build, test, and release immediately? Existing files are never\n" +
120
- "overwritten. [y/N] ",
121
- );
122
- if (!wantDemo) {
123
- return { scaffolded: false, reason: "declined", wrote: [], skipped: [] };
124
- }
125
-
146
+ // Seed a minimal starter into a new project: a src/ module, a README with
147
+ // doc-generator markers, and a project.json — each independently and only when
148
+ // its slot is empty, so nothing a consumer already has is overwritten. Writes
149
+ // files only and returns what it did; the caller reports and prints next steps.
150
+ function scaffoldDemo({ cwd }) {
126
151
  const demoTemplate = resolve(templatesDir, "demo");
127
152
  const wrote = [];
128
153
  const skipped = [];
@@ -152,13 +177,5 @@ async function maybeScaffoldDemo({ cwd, prompt, log }) {
152
177
  }
153
178
  }
154
179
 
155
- log("");
156
- if (wrote.length) log(`Demo: seeded ${wrote.join(", ")}.`);
157
- if (skipped.length) log(`Demo: kept existing ${skipped.join(", ")}.`);
158
- if (paths.length) {
159
- log(`To remove the starter files later: rm -r ${paths.join(" ")}`);
160
- log("Then: npm install → npm test → npm run update-all-format → push.");
161
- }
162
-
163
180
  return { scaffolded: paths.length > 0, reason: null, wrote, skipped, paths };
164
181
  }
@@ -38,14 +38,33 @@ export function detectIndent(text) {
38
38
  * added: { scripts: string[], devDependencies: string[], fields: string[] },
39
39
  * skipped: { scripts: string[], devDependencies: string[], fields: string[] }
40
40
  * }} new file text, plus keys added vs. skipped (already present)
41
+ *
42
+ * `replaceDefaults` names placeholder values that `npm init -y` inserts (e.g.
43
+ * `scripts.test: 'echo "Error: no test specified" && exit 1'`, `main: "index.js"`).
44
+ * A key whose current value exactly equals its placeholder is treated as unset,
45
+ * so a canonical value can replace it instead of being skipped as "present".
41
46
  */
42
47
  export function applyToPackageJson(
43
48
  rawText,
44
- { scripts = {}, devDependencies = {}, fields = {} } = {},
49
+ {
50
+ scripts = {},
51
+ devDependencies = {},
52
+ fields = {},
53
+ replaceDefaults = {},
54
+ } = {},
45
55
  ) {
46
56
  const pkg = JSON.parse(rawText);
47
57
  const indent = detectIndent(rawText);
48
58
 
59
+ const stripDefaults = (obj, defaults = {}) => {
60
+ if (!obj) return;
61
+ for (const [key, placeholder] of Object.entries(defaults)) {
62
+ if (obj[key] === placeholder) delete obj[key];
63
+ }
64
+ };
65
+ stripDefaults(pkg, replaceDefaults.fields);
66
+ stripDefaults(pkg.scripts, replaceDefaults.scripts);
67
+
49
68
  const s = mergeAbsent(pkg.scripts, scripts);
50
69
  const d = mergeAbsent(pkg.devDependencies, devDependencies);
51
70
  const f = mergeAbsent(pkg, fields);