create-packkit 2.7.2 → 2.8.0
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 +30 -0
- package/package.json +5 -3
- package/src/cli/args.js +10 -0
- package/src/cli/index.js +113 -5
- package/src/cli/write.js +103 -8
- package/src/core/node-versions.js +1 -1
package/README.md
CHANGED
|
@@ -31,6 +31,36 @@ npx create-packkit --preset full my-pkg --pm pnpm
|
|
|
31
31
|
|
|
32
32
|
Then `cd`, and you already have a working project — `build`, `test`, and `lint` all pass out of the box.
|
|
33
33
|
|
|
34
|
+
## Create the repo, not just the folder
|
|
35
|
+
|
|
36
|
+
Packkit can create the remote and push the first commit, so you don't have to make an empty repo in a browser first:
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
# create it on GitHub (private) and push
|
|
40
|
+
npx create-packkit ts-lib my-lib --github
|
|
41
|
+
|
|
42
|
+
# public instead
|
|
43
|
+
npx create-packkit ts-lib my-lib --github --public
|
|
44
|
+
|
|
45
|
+
# any other host — GitLab, Bitbucket, Gitea, self-hosted
|
|
46
|
+
npx create-packkit ts-lib my-lib --git-remote git@bitbucket.org:me/my-lib.git
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`--github` shells out to the [GitHub CLI](https://cli.github.com), so **Packkit never asks for, reads, or stores a token** — `gh` already holds your credentials. Created repos are **private unless you pass `--public`**.
|
|
50
|
+
|
|
51
|
+
This also fixes your links: the repository URL is baked into `package.json` and the README's CI badges when the files are generated, so letting Packkit resolve it up front means the badges point somewhere real from the first commit.
|
|
52
|
+
|
|
53
|
+
### Scaffolding into a repo you already have
|
|
54
|
+
|
|
55
|
+
Already cloned an empty repo, or started some work? `--merge` scaffolds around what's there:
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
git clone git@github.com:me/my-lib.git && cd my-lib
|
|
59
|
+
npx create-packkit ts-lib my-lib --here --merge
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**Existing files are never overwritten.** Anything that collides is left alone and reported, so you can diff at your leisure. (A directory containing only `.git` counts as empty — a fresh clone scaffolds without needing `--merge` at all.)
|
|
63
|
+
|
|
34
64
|
## Or configure it on the web
|
|
35
65
|
|
|
36
66
|
No install needed: **[danmat.github.io/create-packkit](https://danmat.github.io/create-packkit/)** — tick the options, preview the file tree, and **download a zip** (or copy the equivalent `npx create-packkit` command). Everything runs in your browser.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-packkit",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.8.0",
|
|
4
4
|
"description": "Highly configurable scaffolder for modern npm packages and CLIs — pick your stack (TS/JS, bundler, tests, linter, CI, releases) from a CLI or a web configurator.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
"exports": {
|
|
10
10
|
".": "./src/core/index.js",
|
|
11
11
|
"./core": "./src/core/index.js",
|
|
12
|
-
"./cli": "./src/cli/index.js"
|
|
12
|
+
"./cli": "./src/cli/index.js",
|
|
13
|
+
"./scaffold": "./src/cli/write.js"
|
|
13
14
|
},
|
|
14
15
|
"files": [
|
|
15
16
|
"bin",
|
|
@@ -28,7 +29,8 @@
|
|
|
28
29
|
"integration": "node scripts/integration.mjs",
|
|
29
30
|
"build:web": "esbuild src/core/index.js --bundle --format=esm --outfile=docs/packkit-core.js",
|
|
30
31
|
"update:node": "node scripts/update-node-versions.mjs",
|
|
31
|
-
"gen:reference": "node scripts/gen-reference.mjs"
|
|
32
|
+
"gen:reference": "node scripts/gen-reference.mjs",
|
|
33
|
+
"sync:mcp": "node scripts/sync-mcp-version.mjs"
|
|
32
34
|
},
|
|
33
35
|
"repository": {
|
|
34
36
|
"type": "git",
|
package/src/cli/args.js
CHANGED
|
@@ -43,6 +43,10 @@ export function parseCliArgs(argv) {
|
|
|
43
43
|
yes: { type: 'boolean', short: 'y' },
|
|
44
44
|
recommended: { type: 'boolean' },
|
|
45
45
|
here: { type: 'boolean' },
|
|
46
|
+
merge: { type: 'boolean' },
|
|
47
|
+
github: { type: 'boolean' },
|
|
48
|
+
'git-remote': { type: 'string' },
|
|
49
|
+
public: { type: 'boolean' },
|
|
46
50
|
'no-install': { type: 'boolean' },
|
|
47
51
|
'no-git': { type: 'boolean' },
|
|
48
52
|
minify: { type: 'boolean' },
|
|
@@ -104,6 +108,12 @@ export function parseCliArgs(argv) {
|
|
|
104
108
|
from: values.from,
|
|
105
109
|
name,
|
|
106
110
|
here: !!values.here,
|
|
111
|
+
merge: !!values.merge,
|
|
112
|
+
github: !!values.github,
|
|
113
|
+
gitRemote: values['git-remote'] || null,
|
|
114
|
+
// Publishing code outward is opt-in, and private is the safe default — a
|
|
115
|
+
// wrong guess here leaks source, so --public must be asked for explicitly.
|
|
116
|
+
private: !values.public,
|
|
107
117
|
yes: !!values.yes || !!values.recommended,
|
|
108
118
|
hasConfigFlags,
|
|
109
119
|
install: !values['no-install'],
|
package/src/cli/index.js
CHANGED
|
@@ -6,7 +6,18 @@ import { generate, fromPreset, normalizeConfig, PRESET_NAMES, OPTIONS, OPTION_HE
|
|
|
6
6
|
import { engineFloor, meetsNodeFloor } from '../core/node.js';
|
|
7
7
|
import { parseCliArgs } from './args.js';
|
|
8
8
|
import { runWizard } from './wizard.js';
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
writeProject,
|
|
11
|
+
existingEntries,
|
|
12
|
+
gitInit,
|
|
13
|
+
installDeps,
|
|
14
|
+
hasCommand,
|
|
15
|
+
githubLogin,
|
|
16
|
+
createGithubRepo,
|
|
17
|
+
pushToRemote,
|
|
18
|
+
writeLockfile,
|
|
19
|
+
commitAll,
|
|
20
|
+
} from './write.js';
|
|
10
21
|
|
|
11
22
|
const pkgVersion = () => {
|
|
12
23
|
try {
|
|
@@ -38,9 +49,15 @@ Getting started:
|
|
|
38
49
|
-y, --yes Accept defaults / preset, no prompts (one-shot)
|
|
39
50
|
--recommended Alias for -y
|
|
40
51
|
--here Scaffold into the current directory
|
|
52
|
+
--merge Scaffold into a non-empty directory (never overwrites)
|
|
41
53
|
--no-install Skip dependency install
|
|
42
54
|
--no-git Skip git init
|
|
43
55
|
|
|
56
|
+
Create the repo:
|
|
57
|
+
--github Create it on GitHub and push (uses the gh CLI)
|
|
58
|
+
--git-remote <url> Push to an existing remote (GitLab, Bitbucket, self-hosted)
|
|
59
|
+
--public Make the created repo public (default: private)
|
|
60
|
+
|
|
44
61
|
Stack:
|
|
45
62
|
--language <ts|js>
|
|
46
63
|
--module <esm|cjs|dual> ESM-only is the default
|
|
@@ -144,14 +161,29 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
144
161
|
console.error('A package name is required (pass one, or run interactively).');
|
|
145
162
|
process.exit(1);
|
|
146
163
|
}
|
|
147
|
-
|
|
148
|
-
|
|
164
|
+
const occupied = existingEntries(targetDir);
|
|
165
|
+
if (occupied.length && !args.merge) {
|
|
166
|
+
const sample = occupied.slice(0, 4).join(', ') + (occupied.length > 4 ? ', …' : '');
|
|
167
|
+
console.error(
|
|
168
|
+
`Target directory "${basename(targetDir)}" is not empty (${sample}).\n` +
|
|
169
|
+
`Re-run with --merge to scaffold around what's there — existing files are never overwritten.`,
|
|
170
|
+
);
|
|
149
171
|
process.exit(1);
|
|
150
172
|
}
|
|
151
173
|
|
|
174
|
+
// Resolve the remote *before* generating: the repository URL is baked into
|
|
175
|
+
// package.json links and README badges at generate time, so creating the repo
|
|
176
|
+
// afterwards would ship a first commit pointing at nothing.
|
|
177
|
+
const remote = resolveRemote(args, config);
|
|
178
|
+
if (remote?.error) {
|
|
179
|
+
console.error(remote.error);
|
|
180
|
+
process.exit(1);
|
|
181
|
+
}
|
|
182
|
+
if (remote?.url && /^https?:/.test(remote.url)) config.repo = remote.url;
|
|
183
|
+
|
|
152
184
|
// Generate + write.
|
|
153
185
|
const { files, summary } = generate(config);
|
|
154
|
-
await writeProject(targetDir, files);
|
|
186
|
+
const { skipped } = await writeProject(targetDir, files, { merge: args.merge });
|
|
155
187
|
|
|
156
188
|
// Post steps.
|
|
157
189
|
if (config.gitInit) gitInit(targetDir);
|
|
@@ -162,6 +194,40 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
162
194
|
s.stop(ok ? 'Dependencies installed' : 'Install skipped (run it manually)');
|
|
163
195
|
}
|
|
164
196
|
|
|
197
|
+
// Create the remote last, so a failure here still leaves a complete local
|
|
198
|
+
// project behind — nothing to clean up, just a command to re-run.
|
|
199
|
+
let pushedTo = null;
|
|
200
|
+
if (remote) {
|
|
201
|
+
const s = p.spinner();
|
|
202
|
+
// A repo pushed without a lockfile fails CI immediately — actions/setup-node
|
|
203
|
+
// errors when its cache can't find one. Produce it without a full install.
|
|
204
|
+
if (!config.install) {
|
|
205
|
+
s.start('Writing a lockfile so CI passes on the first run');
|
|
206
|
+
const ok = writeLockfile(config.packageManager, targetDir);
|
|
207
|
+
s.stop(ok ? 'Lockfile written' : `No lockfile — run \`${config.packageManager} install\` and commit it, or CI will fail`);
|
|
208
|
+
}
|
|
209
|
+
commitAll(targetDir, 'Add lockfile');
|
|
210
|
+
s.start(remote.kind === 'github' ? `Creating ${remote.slug} on GitHub` : 'Pushing to origin');
|
|
211
|
+
const res =
|
|
212
|
+
remote.kind === 'github'
|
|
213
|
+
? createGithubRepo({
|
|
214
|
+
slug: remote.slug,
|
|
215
|
+
description: config.description,
|
|
216
|
+
private: args.private,
|
|
217
|
+
cwd: targetDir,
|
|
218
|
+
})
|
|
219
|
+
: pushToRemote(remote.url, targetDir);
|
|
220
|
+
s.stop(res.ok ? `Pushed to ${remote.url}` : 'Could not create the remote');
|
|
221
|
+
if (res.ok) pushedTo = remote.url;
|
|
222
|
+
else {
|
|
223
|
+
const retry =
|
|
224
|
+
remote.kind === 'github'
|
|
225
|
+
? `gh repo create ${remote.slug} --${args.private ? 'private' : 'public'} --source . --remote origin --push`
|
|
226
|
+
: `git remote add origin ${remote.url} && git push -u origin HEAD`;
|
|
227
|
+
console.error(`\n${res.error || 'The command failed.'}\n\nThe project is scaffolded. To retry:\n cd ${basename(targetDir)} && ${retry}`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
165
231
|
const rel = args.here ? '.' : config.name;
|
|
166
232
|
const next = [
|
|
167
233
|
args.here ? null : `cd ${rel}`,
|
|
@@ -178,9 +244,14 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
178
244
|
? `This is a ${config.framework} component library — \`${runWord(config)} dev\` rebuilds on change (there's no dev server). For a runnable app, scaffold the "${config.framework}-app" preset instead.`
|
|
179
245
|
: null,
|
|
180
246
|
`Requires Node >= ${floor}${nodeOk ? '' : ` — you're on ${process.version}, upgrade first`}.`,
|
|
247
|
+
skipped.length
|
|
248
|
+
? `Kept ${skipped.length} existing file${skipped.length > 1 ? 's' : ''} — Packkit's version was not written: ${skipped.join(', ')}`
|
|
249
|
+
: null,
|
|
181
250
|
].filter(Boolean);
|
|
182
251
|
|
|
183
|
-
const done =
|
|
252
|
+
const done =
|
|
253
|
+
`Created ${summary.name} — ${summary.fileCount - skipped.length} files · ${summary.stack.join(' · ')}` +
|
|
254
|
+
(pushedTo ? `\n${pushedTo}` : '');
|
|
184
255
|
if (interactive) {
|
|
185
256
|
p.note(next.join('\n') || 'You are all set.', 'Next steps');
|
|
186
257
|
if (hints.length) p.log.info(hints.join('\n'));
|
|
@@ -199,6 +270,43 @@ function runWord(config) {
|
|
|
199
270
|
return config.packageManager === 'npm' ? 'npm run' : config.packageManager;
|
|
200
271
|
}
|
|
201
272
|
|
|
273
|
+
// owner/name for the repo to create: an explicit --repo URL wins, otherwise the
|
|
274
|
+
// authenticated gh account plus the project name.
|
|
275
|
+
function githubSlug(repoUrl, login, name) {
|
|
276
|
+
const m = /github\.com[/:]([^/]+)\/([^/]+?)(?:\.git)?\/*$/.exec(repoUrl || '');
|
|
277
|
+
if (m) return `${m[1]}/${m[2]}`;
|
|
278
|
+
return login ? `${login}/${name}` : null;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Work out what remote to create, if any. Returns null (nothing to do),
|
|
283
|
+
* { error } to abort before writing anything, or the resolved target.
|
|
284
|
+
*/
|
|
285
|
+
function resolveRemote(args, config) {
|
|
286
|
+
if (!args.github && !args.gitRemote) return null;
|
|
287
|
+
if (!config.gitInit) {
|
|
288
|
+
return { error: 'Creating a repo needs a local git repo — drop --no-git.' };
|
|
289
|
+
}
|
|
290
|
+
if (args.gitRemote) return { kind: 'remote', url: args.gitRemote };
|
|
291
|
+
|
|
292
|
+
// Delegate to `gh` rather than calling the API: it already holds the user's
|
|
293
|
+
// credentials, so Packkit never reads, prompts for, or stores a token.
|
|
294
|
+
if (!hasCommand('gh')) {
|
|
295
|
+
return {
|
|
296
|
+
error:
|
|
297
|
+
'--github needs the GitHub CLI. Install it (https://cli.github.com), or use\n' +
|
|
298
|
+
'--git-remote <url> to push to a repo you have already created.',
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
const login = githubLogin();
|
|
302
|
+
if (!login) {
|
|
303
|
+
return { error: '--github needs an authenticated GitHub CLI. Run: gh auth login' };
|
|
304
|
+
}
|
|
305
|
+
const slug = githubSlug(config.repo, login, config.name);
|
|
306
|
+
if (!slug) return { error: 'Could not work out the repository name. Pass --repo <url>.' };
|
|
307
|
+
return { kind: 'github', slug, url: `https://github.com/${slug}` };
|
|
308
|
+
}
|
|
309
|
+
|
|
202
310
|
// Load a partial config from --from <file>, or a packkit.config.json in cwd.
|
|
203
311
|
function loadProfile(args) {
|
|
204
312
|
const path = args.from || (existsSync('packkit.config.json') ? 'packkit.config.json' : null);
|
package/src/cli/write.js
CHANGED
|
@@ -3,26 +3,48 @@ import { existsSync, readdirSync } from 'node:fs';
|
|
|
3
3
|
import { dirname, join } from 'node:path';
|
|
4
4
|
import { spawnSync } from 'node:child_process';
|
|
5
5
|
|
|
6
|
-
/**
|
|
7
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Write a { path: contents } map under `targetDir`.
|
|
8
|
+
* In `merge` mode an existing file is never touched — it's reported as skipped,
|
|
9
|
+
* so scaffolding into a repo that already has files can't destroy work.
|
|
10
|
+
* Returns { written, skipped } as arrays of relative paths.
|
|
11
|
+
*/
|
|
12
|
+
export async function writeProject(targetDir, files, { merge = false } = {}) {
|
|
13
|
+
const written = [];
|
|
14
|
+
const skipped = [];
|
|
8
15
|
for (const [rel, contents] of Object.entries(files)) {
|
|
9
16
|
const full = join(targetDir, rel);
|
|
17
|
+
if (merge && existsSync(full)) {
|
|
18
|
+
skipped.push(rel);
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
10
21
|
await mkdir(dirname(full), { recursive: true });
|
|
11
22
|
await writeFile(full, contents);
|
|
23
|
+
written.push(rel);
|
|
12
24
|
}
|
|
13
|
-
return
|
|
25
|
+
return { written, skipped };
|
|
14
26
|
}
|
|
15
27
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
28
|
+
// Entries that don't make a directory "occupied": VCS metadata and OS noise.
|
|
29
|
+
// `git clone` of a fresh empty repo leaves .git/ behind, and treating that as
|
|
30
|
+
// non-empty broke the most common flow — create the repo, clone it, scaffold in.
|
|
31
|
+
const IGNORED_ENTRIES = new Set(['.git', '.DS_Store', 'Thumbs.db', '.hg', '.svn']);
|
|
32
|
+
|
|
33
|
+
/** Real (non-ignorable) entries in `dir`. Empty when the dir is missing. */
|
|
34
|
+
export function existingEntries(dir) {
|
|
35
|
+
if (!existsSync(dir)) return [];
|
|
19
36
|
try {
|
|
20
|
-
return readdirSync(dir).
|
|
37
|
+
return readdirSync(dir).filter((e) => !IGNORED_ENTRIES.has(e));
|
|
21
38
|
} catch {
|
|
22
|
-
return
|
|
39
|
+
return [];
|
|
23
40
|
}
|
|
24
41
|
}
|
|
25
42
|
|
|
43
|
+
/** True if the directory doesn't exist or holds nothing that would be clobbered. */
|
|
44
|
+
export function dirIsEmptyOrMissing(dir) {
|
|
45
|
+
return existingEntries(dir).length === 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
26
48
|
/** Run a command in `cwd` (arg array — no shell parsing). Never throws. */
|
|
27
49
|
export function run(cmd, args, cwd, { quiet = true } = {}) {
|
|
28
50
|
const res = spawnSync(cmd, args, {
|
|
@@ -44,3 +66,76 @@ export function gitInit(cwd) {
|
|
|
44
66
|
export function installDeps(pm, cwd) {
|
|
45
67
|
return run(pm, ['install'], cwd, { quiet: false });
|
|
46
68
|
}
|
|
69
|
+
|
|
70
|
+
// Resolving the dependency graph without downloading it. Yarn has no equivalent
|
|
71
|
+
// that works across its v1/v2+ split, so it's left out and reported instead.
|
|
72
|
+
const LOCKFILE_ONLY = {
|
|
73
|
+
npm: ['install', '--package-lock-only', '--ignore-scripts'],
|
|
74
|
+
pnpm: ['install', '--lockfile-only', '--ignore-scripts'],
|
|
75
|
+
bun: ['install', '--lockfile-only'],
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Write a lockfile without installing.
|
|
80
|
+
*
|
|
81
|
+
* A pushed repo with no lockfile fails CI on its very first run — `cache: npm`
|
|
82
|
+
* in actions/setup-node errors outright when it can't find one. So when we're
|
|
83
|
+
* about to create a remote but the install was skipped, produce the lockfile
|
|
84
|
+
* anyway. Returns false when the package manager has no such mode.
|
|
85
|
+
*/
|
|
86
|
+
export function writeLockfile(pm, cwd) {
|
|
87
|
+
const args = LOCKFILE_ONLY[pm];
|
|
88
|
+
return args ? run(pm, args, cwd) : false;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Run a command and capture its output. Never throws. */
|
|
92
|
+
function capture(cmd, args, cwd) {
|
|
93
|
+
const res = spawnSync(cmd, args, { cwd, encoding: 'utf8', shell: process.platform === 'win32' });
|
|
94
|
+
return {
|
|
95
|
+
ok: res.status === 0,
|
|
96
|
+
stdout: (res.stdout || '').trim(),
|
|
97
|
+
stderr: (res.stderr || '').trim(),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** True if `cmd` is on PATH. */
|
|
102
|
+
export function hasCommand(cmd) {
|
|
103
|
+
return capture(process.platform === 'win32' ? 'where' : 'which', [cmd], process.cwd()).ok;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** The authenticated GitHub login, or null if gh isn't installed or logged in. */
|
|
107
|
+
export function githubLogin() {
|
|
108
|
+
const res = capture('gh', ['api', 'user', '-q', '.login'], process.cwd());
|
|
109
|
+
return res.ok && res.stdout ? res.stdout : null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Create the GitHub repo and push to it, via the `gh` CLI.
|
|
114
|
+
*
|
|
115
|
+
* Deliberately shells out rather than calling the REST API: `gh` already owns
|
|
116
|
+
* the user's credentials, so Packkit never reads, prompts for, or stores a
|
|
117
|
+
* token. Returns { ok, error }.
|
|
118
|
+
*/
|
|
119
|
+
export function createGithubRepo({ slug, description, private: isPrivate = true, cwd }) {
|
|
120
|
+
const args = ['repo', 'create', slug, isPrivate ? '--private' : '--public',
|
|
121
|
+
'--source', '.', '--remote', 'origin', '--push'];
|
|
122
|
+
if (description) args.push('--description', description);
|
|
123
|
+
const res = capture('gh', args, cwd);
|
|
124
|
+
return { ok: res.ok, error: res.stderr || res.stdout };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Point `origin` at an existing remote URL and push the current branch. */
|
|
128
|
+
export function pushToRemote(url, cwd) {
|
|
129
|
+
const added = capture('git', ['remote', 'add', 'origin', url], cwd);
|
|
130
|
+
if (!added.ok) return { ok: false, error: added.stderr };
|
|
131
|
+
const branch = capture('git', ['rev-parse', '--abbrev-ref', 'HEAD'], cwd);
|
|
132
|
+
const res = capture('git', ['push', '-u', 'origin', branch.ok ? branch.stdout : 'main'], cwd);
|
|
133
|
+
return { ok: res.ok, error: res.stderr };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Stage and commit everything if the tree is dirty. No-op on a clean tree. */
|
|
137
|
+
export function commitAll(cwd, message) {
|
|
138
|
+
if (!capture('git', ['status', '--porcelain'], cwd).stdout) return false;
|
|
139
|
+
run('git', ['add', '-A'], cwd);
|
|
140
|
+
return run('git', ['commit', '-m', message, '--quiet'], cwd);
|
|
141
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// AUTO-GENERATED by scripts/update-node-versions.mjs — do not edit by hand.
|
|
2
|
-
// Source: nodejs.org/dist + nodejs/Release schedule, refreshed 2026-07-
|
|
2
|
+
// Source: nodejs.org/dist + nodejs/Release schedule, refreshed 2026-07-20.
|
|
3
3
|
// Offered lines track the Node release schedule; each version is that line's
|
|
4
4
|
// newest published patch. Run `node scripts/update-node-versions.mjs` to refresh.
|
|
5
5
|
|