@doikayt/typescript-build-config 0.1.18 → 0.1.20
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 +8 -5
- package/package.json +1 -1
- package/src/canonical-scripts.js +10 -1
- package/src/init.js +60 -17
- package/src/pipeline/auto-changeset.sh +3 -1
- package/src/postinstall.js +1 -10
package/README.md
CHANGED
|
@@ -123,7 +123,9 @@ small single-purpose scripts, and the check side mirrors the write side:
|
|
|
123
123
|
publish config accordingly (non-destructively):
|
|
124
124
|
|
|
125
125
|
- **Library** — `main` / `types` / `exports` → `dist/index.js`, `files: ["dist"]`,
|
|
126
|
-
and `prepack`, so it publishes a compiled, typed ESM package on push.
|
|
126
|
+
and `prepack`, so it publishes a compiled, typed ESM package on push. `init`
|
|
127
|
+
also prompts for the package name (default `@doikayt/<name>`; a bare name is
|
|
128
|
+
scoped to `@doikayt`, an already-scoped name is kept).
|
|
127
129
|
- **App / CLI** (the default) — marked `private: true`, so a release versions and
|
|
128
130
|
tags it but never publishes to npm.
|
|
129
131
|
|
|
@@ -142,8 +144,9 @@ npm install # fetch the declared devD
|
|
|
142
144
|
|
|
143
145
|
`init` writes the canonical scripts (`ci`, `build`, `test`, `update-all-format`,
|
|
144
146
|
`check-all-format`, and their sub-tasks), declares `vitest` +
|
|
145
|
-
`@doikayt/autogen-markdown-doc` + `typescript
|
|
146
|
-
`vitest.config.ts`. Answering the
|
|
147
|
+
`@doikayt/autogen-markdown-doc` + `typescript` + `@changesets/cli` (the release
|
|
148
|
+
pipeline needs it), and seeds a commented `vitest.config.ts`. Answering the
|
|
149
|
+
"publishable library?" prompt sets the publish
|
|
147
150
|
config — `private` for an app, or `prepack` + the `dist/`-based fields for a
|
|
148
151
|
library (see [The canonical script set](#the-canonical-script-set)).
|
|
149
152
|
|
|
@@ -336,8 +339,8 @@ rely on it.
|
|
|
336
339
|
## Release Pipeline
|
|
337
340
|
|
|
338
341
|
On install, the postinstall script copies the release pipeline files into your
|
|
339
|
-
project automatically.
|
|
340
|
-
|
|
342
|
+
project automatically. `auto-changeset.sh` reads the package name from your
|
|
343
|
+
`package.json` at runtime, so renaming the package needs no pipeline edits.
|
|
341
344
|
|
|
342
345
|
On `npm update`, the behavior per file is:
|
|
343
346
|
|
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.20",
|
|
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
|
@@ -31,7 +31,16 @@ export function canonicalScripts({ ui = false, library = false } = {}) {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
export function devDependencyNames({ ui = false } = {}) {
|
|
34
|
-
|
|
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
|
@@ -62,21 +62,29 @@ function createAskYesNo() {
|
|
|
62
62
|
closed = true;
|
|
63
63
|
while (waiters.length) waiters.shift()("");
|
|
64
64
|
});
|
|
65
|
-
const
|
|
65
|
+
const nextLine = (question) =>
|
|
66
66
|
new Promise((res) => {
|
|
67
67
|
process.stdout.write(question);
|
|
68
|
-
|
|
69
|
-
if (
|
|
70
|
-
else
|
|
71
|
-
else waiters.push(answer);
|
|
68
|
+
if (lines.length) res(lines.shift());
|
|
69
|
+
else if (closed) res("");
|
|
70
|
+
else waiters.push(res);
|
|
72
71
|
});
|
|
72
|
+
const ask = async (question) =>
|
|
73
|
+
/^y(es)?$/i.test((await nextLine(question)).trim());
|
|
74
|
+
ask.text = async (question) => (await nextLine(question)).trim();
|
|
73
75
|
ask.close = () => rl.close();
|
|
74
76
|
return ask;
|
|
75
77
|
}
|
|
76
78
|
|
|
79
|
+
// Prepend the @doikayt scope unless the name is already scoped.
|
|
80
|
+
function scopeName(name) {
|
|
81
|
+
return name.startsWith("@") ? name : `@doikayt/${name}`;
|
|
82
|
+
}
|
|
83
|
+
|
|
77
84
|
export async function runInit({
|
|
78
85
|
cwd = process.cwd(),
|
|
79
86
|
prompt,
|
|
87
|
+
promptText,
|
|
80
88
|
resolveDevVersions = resolveVersions,
|
|
81
89
|
log = console.log,
|
|
82
90
|
} = {}) {
|
|
@@ -84,16 +92,32 @@ export async function runInit({
|
|
|
84
92
|
// on error so a leftover interface never keeps the process alive.
|
|
85
93
|
const ownAsk = prompt ? null : createAskYesNo();
|
|
86
94
|
const ask = prompt ?? ownAsk;
|
|
95
|
+
// Free-text prompt (package name). Falls back to "" (accept the default) when a
|
|
96
|
+
// yes/no prompt is injected without a text one, so tests need not supply it.
|
|
97
|
+
const askText = promptText ?? ownAsk?.text ?? (async () => "");
|
|
87
98
|
try {
|
|
88
|
-
return await collectAndScaffold({
|
|
99
|
+
return await collectAndScaffold({
|
|
100
|
+
cwd,
|
|
101
|
+
ask,
|
|
102
|
+
askText,
|
|
103
|
+
resolveDevVersions,
|
|
104
|
+
log,
|
|
105
|
+
});
|
|
89
106
|
} finally {
|
|
90
107
|
ownAsk?.close();
|
|
91
108
|
}
|
|
92
109
|
}
|
|
93
110
|
|
|
94
|
-
async function collectAndScaffold({
|
|
111
|
+
async function collectAndScaffold({
|
|
112
|
+
cwd,
|
|
113
|
+
ask,
|
|
114
|
+
askText,
|
|
115
|
+
resolveDevVersions,
|
|
116
|
+
log,
|
|
117
|
+
}) {
|
|
95
118
|
const pkgPath = resolve(cwd, "package.json");
|
|
96
119
|
const raw = readFileSync(pkgPath, "utf8");
|
|
120
|
+
const currentName = JSON.parse(raw).name;
|
|
97
121
|
|
|
98
122
|
// Collect every answer up front, before doing any work or printing next
|
|
99
123
|
// steps, so the flow reads: ask everything → act → report.
|
|
@@ -102,10 +126,21 @@ async function collectAndScaffold({ cwd, ask, resolveDevVersions, log }) {
|
|
|
102
126
|
);
|
|
103
127
|
|
|
104
128
|
const library = await ask(
|
|
105
|
-
"Is this a publishable library (published to npm)? Default is No
|
|
106
|
-
"
|
|
129
|
+
"Is this a publishable library (published to npm)? Default is No.\n" +
|
|
130
|
+
" No = an app/CLI (marked private, never published). [y/N] ",
|
|
107
131
|
);
|
|
108
132
|
|
|
133
|
+
// The package name is what gets published, so ask only for a library. A bare
|
|
134
|
+
// name gets the @doikayt scope; an already-scoped name is kept.
|
|
135
|
+
let libraryName;
|
|
136
|
+
if (library) {
|
|
137
|
+
const suggested = scopeName(currentName || "package");
|
|
138
|
+
const entered = await askText(
|
|
139
|
+
`Package name for publishing? [${suggested}] `,
|
|
140
|
+
);
|
|
141
|
+
libraryName = entered ? scopeName(entered) : suggested;
|
|
142
|
+
}
|
|
143
|
+
|
|
109
144
|
const wantDemo = await ask(
|
|
110
145
|
"Scaffold a starter demo (minimal src module + README + project.json) so\n" +
|
|
111
146
|
"you can build, test, and release immediately? Default is No; existing\n" +
|
|
@@ -115,17 +150,25 @@ async function collectAndScaffold({ cwd, ask, resolveDevVersions, log }) {
|
|
|
115
150
|
const scripts = canonicalScripts({ ui, library });
|
|
116
151
|
const devDependencies = resolveDevVersions(devDependencyNames({ ui }));
|
|
117
152
|
|
|
153
|
+
const fields = packageFields({ library });
|
|
154
|
+
// `npm init -y` seeds these placeholders; treat them as unset so the canonical
|
|
155
|
+
// `test` script and (for a library) `main` replace them instead of shadowing.
|
|
156
|
+
const replaceDefaults = {
|
|
157
|
+
scripts: { test: 'echo "Error: no test specified" && exit 1' },
|
|
158
|
+
fields: { main: "index.js" },
|
|
159
|
+
};
|
|
160
|
+
if (library) {
|
|
161
|
+
// The chosen name is a deliberate publish decision, so it replaces whatever
|
|
162
|
+
// name npm init left in place.
|
|
163
|
+
fields.name = libraryName;
|
|
164
|
+
if (currentName != null) replaceDefaults.fields.name = currentName;
|
|
165
|
+
}
|
|
166
|
+
|
|
118
167
|
const { text, added, skipped } = applyToPackageJson(raw, {
|
|
119
168
|
scripts,
|
|
120
169
|
devDependencies,
|
|
121
|
-
fields
|
|
122
|
-
|
|
123
|
-
// canonical `test` script and (for a library) `main` replace them instead of
|
|
124
|
-
// shadowing them.
|
|
125
|
-
replaceDefaults: {
|
|
126
|
-
scripts: { test: 'echo "Error: no test specified" && exit 1' },
|
|
127
|
-
fields: { main: "index.js" },
|
|
128
|
-
},
|
|
170
|
+
fields,
|
|
171
|
+
replaceDefaults,
|
|
129
172
|
});
|
|
130
173
|
writeFileSync(pkgPath, text);
|
|
131
174
|
|
|
@@ -15,7 +15,9 @@
|
|
|
15
15
|
set -euo pipefail
|
|
16
16
|
|
|
17
17
|
CHANGESET_DIR=".changeset"
|
|
18
|
-
|
|
18
|
+
# Read the package name from package.json at runtime, so it stays correct even if
|
|
19
|
+
# the project is renamed after install — package.json is the single source of truth.
|
|
20
|
+
PACKAGES=("$(node -p "require('./package.json').name")")
|
|
19
21
|
|
|
20
22
|
# Skip if a manually-authored changeset already exists
|
|
21
23
|
EXISTING=$(find "$CHANGESET_DIR" -name "*.md" ! -name "README.md" | wc -l)
|
package/src/postinstall.js
CHANGED
|
@@ -61,8 +61,6 @@ if (alreadyExist.length > 0) {
|
|
|
61
61
|
|
|
62
62
|
// --- Pipeline files: per-file copy/warn/diff ---
|
|
63
63
|
|
|
64
|
-
const projectName = projectPkg.name;
|
|
65
|
-
|
|
66
64
|
const pipelineFiles = [
|
|
67
65
|
{ src: "release.yml", dest: ".github/workflows/release.yml" },
|
|
68
66
|
{ src: "changeset-config.json", dest: ".changeset/config.json" },
|
|
@@ -77,14 +75,7 @@ for (const { src, dest } of pipelineFiles) {
|
|
|
77
75
|
const srcPath = resolve(pipelineDir, src);
|
|
78
76
|
const destPath = resolve(projectRoot, dest);
|
|
79
77
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
if (src === "auto-changeset.sh") {
|
|
83
|
-
canonical = canonical.replace(
|
|
84
|
-
'PACKAGES=("__PACKAGE_NAME__")',
|
|
85
|
-
`PACKAGES=("${projectName}")`,
|
|
86
|
-
);
|
|
87
|
-
}
|
|
78
|
+
const canonical = readFileSync(srcPath, "utf8");
|
|
88
79
|
|
|
89
80
|
if (!existsSync(destPath)) {
|
|
90
81
|
mkdirSync(dirname(destPath), { recursive: true });
|