@doikayt/typescript-build-config 0.1.15 → 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/README.md +22 -10
- package/package.json +1 -1
- package/src/canonical-scripts.js +24 -20
- package/src/init.js +50 -28
- package/src/package-json.js +20 -1
- package/src/templates/demo/README.md +1 -1
package/README.md
CHANGED
|
@@ -111,33 +111,41 @@ small single-purpose scripts, and the check side mirrors the write side:
|
|
|
111
111
|
| --- | --- |
|
|
112
112
|
| `ci` | The release gate: `check-all-format` then `test` (plus `test:e2e` for UI). |
|
|
113
113
|
| `build` | Compile TypeScript to `dist/` (`tsc`). |
|
|
114
|
-
| `prepack` | Runs `build` before packing, so `changeset publish` ships compiled `dist/`. |
|
|
115
114
|
| `test` | Unit / integration tests (`vitest run`). |
|
|
116
115
|
| `test:e2e` | End-to-end tests (`playwright test`) — UI projects only. |
|
|
116
|
+
| `prepack` | Runs `build` before packing, so `changeset publish` ships compiled `dist/` — **libraries only**. |
|
|
117
117
|
| `update-all-format` | Reformat everything: code + markdown docs (write). |
|
|
118
118
|
| `check-all-format` | Verify formatting: code + markdown docs (used by `ci`). |
|
|
119
119
|
| `update-code-formatting` / `check-code-formatting` | Prettier write / check. |
|
|
120
120
|
| `update-markdown-docs` / `check-markdown-docs` | Regenerate / verify generated markdown (TOC, UML, …). |
|
|
121
121
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
122
|
+
`init` also asks whether the project is a **publishable library**, which sets the
|
|
123
|
+
publish config accordingly (non-destructively):
|
|
124
|
+
|
|
125
|
+
- **Library** — `main` / `types` / `exports` → `dist/index.js`, `files: ["dist"]`,
|
|
126
|
+
and `prepack`, so it publishes a compiled, typed ESM package on push.
|
|
127
|
+
- **App / CLI** (the default) — marked `private: true`, so a release versions and
|
|
128
|
+
tags it but never publishes to npm.
|
|
129
|
+
|
|
130
|
+
`type: "module"` and `build` are set either way. New to these terms (compiled
|
|
131
|
+
`dist/`, publish fields, library vs app)? See
|
|
132
|
+
[Packaging concepts](docs/packaging-concepts.md) for the background.
|
|
127
133
|
|
|
128
134
|
### Console / CLI project
|
|
129
135
|
|
|
130
136
|
```bash
|
|
131
137
|
npm init -y # if starting fresh
|
|
132
138
|
npm install --save-dev @doikayt/typescript-build-config
|
|
133
|
-
npx @doikayt/typescript-build-config init # answer "n" to
|
|
139
|
+
npx @doikayt/typescript-build-config init # answer "n" to Playwright; "n" (app) or "y" (library)
|
|
134
140
|
npm install # fetch the declared devDependencies
|
|
135
141
|
```
|
|
136
142
|
|
|
137
143
|
`init` writes the canonical scripts (`ci`, `build`, `test`, `update-all-format`,
|
|
138
|
-
`check-all-format`,
|
|
139
|
-
|
|
140
|
-
`
|
|
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
|
|
147
|
+
config — `private` for an app, or `prepack` + the `dist/`-based fields for a
|
|
148
|
+
library (see [The canonical script set](#the-canonical-script-set)).
|
|
141
149
|
|
|
142
150
|
### UI / web project (with Playwright e2e)
|
|
143
151
|
|
|
@@ -496,6 +504,10 @@ To auto-fix formatting before verifying:
|
|
|
496
504
|
npm run update-all-format && npm run ci
|
|
497
505
|
```
|
|
498
506
|
|
|
507
|
+
To verify end-to-end that `init` scaffolds and ships both a library and an app —
|
|
508
|
+
by hand, with what to look for at each step — follow
|
|
509
|
+
[docs/e2e-verification.md](docs/e2e-verification.md).
|
|
510
|
+
|
|
499
511
|
## License
|
|
500
512
|
|
|
501
513
|
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@doikayt/typescript-build-config",
|
|
3
|
-
"version": "0.1.
|
|
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/canonical-scripts.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
// The canonical npm-script set init writes into every consumer. package.json is
|
|
2
2
|
// the single source of truth (it is always present); NX projects, if any, add
|
|
3
|
-
// thin targets that delegate to `npm run <x>` — never the reverse.
|
|
4
|
-
//
|
|
5
|
-
//
|
|
3
|
+
// thin targets that delegate to `npm run <x>` — never the reverse. Two axes vary
|
|
4
|
+
// the set: UI/console (Playwright e2e) and library/app. `build` is universal
|
|
5
|
+
// (both archetypes compile); only a publishable library adds `prepack`, which
|
|
6
|
+
// builds dist/ at publish time so `changeset publish` ships compiled output.
|
|
6
7
|
|
|
7
|
-
export function canonicalScripts({ ui = false } = {}) {
|
|
8
|
+
export function canonicalScripts({ ui = false, library = false } = {}) {
|
|
8
9
|
const ci = ui
|
|
9
10
|
? "npm run check-all-format && npm run test && npm run test:e2e"
|
|
10
11
|
: "npm run check-all-format && npm run test";
|
|
@@ -21,12 +22,10 @@ export function canonicalScripts({ ui = false } = {}) {
|
|
|
21
22
|
"npm run check-code-formatting && npm run check-markdown-docs",
|
|
22
23
|
"check-code-formatting": "prettier --check src/",
|
|
23
24
|
"check-markdown-docs": "autogen-markdown-doc check",
|
|
24
|
-
// Build at publish time so `changeset publish` ships compiled dist/ without
|
|
25
|
-
// the release workflow needing a build step.
|
|
26
|
-
prepack: "npm run build",
|
|
27
25
|
};
|
|
28
26
|
|
|
29
27
|
if (ui) scripts["test:e2e"] = "doikayt-playwright-install && playwright test";
|
|
28
|
+
if (library) scripts.prepack = "npm run build";
|
|
30
29
|
|
|
31
30
|
return scripts;
|
|
32
31
|
}
|
|
@@ -37,21 +36,26 @@ export function devDependencyNames({ ui = false } = {}) {
|
|
|
37
36
|
return names;
|
|
38
37
|
}
|
|
39
38
|
|
|
40
|
-
// Top-level package.json fields init sets (non-destructively)
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
39
|
+
// Top-level package.json fields init sets (non-destructively). `type: "module"`
|
|
40
|
+
// is universal. A publishable **library** also gets the dist/-based entry points
|
|
41
|
+
// (main/types/exports) and the tarball allow-list (files), assuming the canonical
|
|
42
|
+
// src/index.ts entry. An **app** is instead marked `private` so `changeset
|
|
43
|
+
// publish` never publishes it — its release is a version bump + tag only. Either
|
|
44
|
+
// way, fields a consumer already set are left untouched by the caller.
|
|
45
|
+
export function packageFields({ library = false } = {}) {
|
|
46
|
+
const fields = { type: "module" };
|
|
47
|
+
if (library) {
|
|
48
|
+
fields.main = "dist/index.js";
|
|
49
|
+
fields.types = "dist/index.d.ts";
|
|
50
|
+
fields.exports = {
|
|
50
51
|
".": {
|
|
51
52
|
types: "./dist/index.d.ts",
|
|
52
53
|
default: "./dist/index.js",
|
|
53
54
|
},
|
|
54
|
-
}
|
|
55
|
-
files
|
|
56
|
-
}
|
|
55
|
+
};
|
|
56
|
+
fields.files = ["dist"];
|
|
57
|
+
} else {
|
|
58
|
+
fields.private = true;
|
|
59
|
+
}
|
|
60
|
+
return fields;
|
|
57
61
|
}
|
package/src/init.js
CHANGED
|
@@ -62,17 +62,37 @@ 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
|
-
const
|
|
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] ",
|
|
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] ",
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
const scripts = canonicalScripts({ ui, library });
|
|
70
83
|
const devDependencies = resolveDevVersions(devDependencyNames({ ui }));
|
|
71
84
|
|
|
72
85
|
const { text, added, skipped } = applyToPackageJson(raw, {
|
|
73
86
|
scripts,
|
|
74
87
|
devDependencies,
|
|
75
|
-
fields: packageFields(),
|
|
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
|
+
},
|
|
76
96
|
});
|
|
77
97
|
writeFileSync(pkgPath, text);
|
|
78
98
|
|
|
@@ -85,6 +105,16 @@ export async function runInit({
|
|
|
85
105
|
(result === "seeded" ? seededConfigs : keptConfigs).push(name);
|
|
86
106
|
}
|
|
87
107
|
|
|
108
|
+
const demo = wantDemo
|
|
109
|
+
? scaffoldDemo({ cwd })
|
|
110
|
+
: {
|
|
111
|
+
scaffolded: false,
|
|
112
|
+
reason: "declined",
|
|
113
|
+
wrote: [],
|
|
114
|
+
skipped: [],
|
|
115
|
+
paths: [],
|
|
116
|
+
};
|
|
117
|
+
|
|
88
118
|
const report = (label, keys) => {
|
|
89
119
|
if (keys.length) log(`${label}: ${keys.join(", ")}`);
|
|
90
120
|
};
|
|
@@ -96,28 +126,28 @@ export async function runInit({
|
|
|
96
126
|
report("Kept existing package fields", skipped.fields);
|
|
97
127
|
report("Seeded config files", seededConfigs);
|
|
98
128
|
report("Kept existing config files", keptConfigs);
|
|
99
|
-
|
|
100
|
-
|
|
129
|
+
report("Demo seeded", demo.wrote);
|
|
130
|
+
report("Demo kept existing", demo.skipped);
|
|
101
131
|
|
|
102
|
-
|
|
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
|
+
}
|
|
103
142
|
|
|
104
143
|
return { added, skipped, seededConfigs, keptConfigs, demo };
|
|
105
144
|
}
|
|
106
145
|
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
//
|
|
110
|
-
//
|
|
111
|
-
|
|
112
|
-
const wantDemo = await prompt(
|
|
113
|
-
"Scaffold a starter demo (minimal src module + README + project.json) so you\n" +
|
|
114
|
-
"can build, test, and publish immediately? Existing files are never\n" +
|
|
115
|
-
"overwritten. [y/N] ",
|
|
116
|
-
);
|
|
117
|
-
if (!wantDemo) {
|
|
118
|
-
return { scaffolded: false, reason: "declined", wrote: [], skipped: [] };
|
|
119
|
-
}
|
|
120
|
-
|
|
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 }) {
|
|
121
151
|
const demoTemplate = resolve(templatesDir, "demo");
|
|
122
152
|
const wrote = [];
|
|
123
153
|
const skipped = [];
|
|
@@ -147,13 +177,5 @@ async function maybeScaffoldDemo({ cwd, prompt, log }) {
|
|
|
147
177
|
}
|
|
148
178
|
}
|
|
149
179
|
|
|
150
|
-
log("");
|
|
151
|
-
if (wrote.length) log(`Demo: seeded ${wrote.join(", ")}.`);
|
|
152
|
-
if (skipped.length) log(`Demo: kept existing ${skipped.join(", ")}.`);
|
|
153
|
-
if (paths.length) {
|
|
154
|
-
log(`To remove the starter files later: rm -r ${paths.join(" ")}`);
|
|
155
|
-
log("Then: npm install → npm test → npm run update-all-format → push.");
|
|
156
|
-
}
|
|
157
|
-
|
|
158
180
|
return { scaffolded: paths.length > 0, reason: null, wrote, skipped, paths };
|
|
159
181
|
}
|
package/src/package-json.js
CHANGED
|
@@ -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
|
-
{
|
|
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);
|
|
@@ -38,5 +38,5 @@ Mermaid diagrams inline from the fenced blocks.
|
|
|
38
38
|
npm install # fetch the dev deps init declared
|
|
39
39
|
npm test # the seeded vitest test passes
|
|
40
40
|
npm run update-all-format # fills the doc blocks above
|
|
41
|
-
git commit -am "feat: initial version" && git push # feat:/fix:
|
|
41
|
+
git commit -am "feat: initial version" && git push # feat:/fix: cut a release (npm publish if a library)
|
|
42
42
|
```
|