Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions docs/packkit-core.js
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,22 @@ function readme(cfg) {
if (cfg.hasCli) {
lines.push("## CLI", "", "```sh", `npx ${cfg.name} --help`, "```", "");
}
if (cfg.workflows?.includes("npm-publish")) {
const changesets = cfg.release === "changesets";
lines.push(
"## Releasing",
"",
changesets ? "Releases are handled by the `Release` workflow: push a changeset (`npx changeset`) and it opens a version PR, then publishes when that PR merges." : "Pushing a `v*` tag triggers the `Publish` workflow, which publishes to npm with provenance.",
"",
"**Publishing needs npm credentials, which a new repository does not have.** Either:",
"",
"- Set up [npm Trusted Publishing](https://docs.npmjs.com/trusted-publishers) (OIDC) \u2014 no secret to store or rotate, and the recommended option; or",
"- Add an [npm automation token](https://docs.npmjs.com/creating-and-viewing-access-tokens) as an `NPM_TOKEN` repository secret.",
"",
changesets ? "Until one of those is in place the `Release` workflow will fail with `ENEEDAUTH` on every push. That is expected on a brand-new repo." : "Until one of those is in place, tag pushes will fail with `ENEEDAUTH`.",
""
);
}
lines.push("## License", "", cfg.license === "none" ? "Unlicensed." : `${cfg.license}${cfg.author ? " \xA9 " + cfg.author : ""}`, "");
return lines.join("\n");
}
Expand Down
5 changes: 5 additions & 0 deletions src/cli/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,11 @@ export async function run(argv = process.argv.slice(2)) {
skipped.length
? `Kept ${skipped.length} existing file${skipped.length > 1 ? 's' : ''} — Packkit's version was not written: ${skipped.join(', ')}`
: null,
// Said here too, not just in the README: this is the moment a red X appears
// on a repo that is ten seconds old, and the cause is not obvious.
pushedTo && config.workflows?.includes('npm-publish') && config.release === 'changesets'
? `The Release workflow will fail until npm credentials exist — set up npm Trusted Publishing, or add an NPM_TOKEN secret. See "Releasing" in the README.`
: null,
].filter(Boolean);

const done =
Expand Down
25 changes: 25 additions & 0 deletions src/core/features/meta.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,31 @@ function readme(cfg) {
lines.push('## CLI', '', '```sh', `npx ${cfg.name} --help`, '```', '');
}

// Publishing needs a credential the repo doesn't have yet. The changesets
// workflow runs on every push, so without this note the first thing a new
// repo does is fail a job for a reason that's only explained in a YAML
// comment. Say it where someone will actually read it.
if (cfg.workflows?.includes('npm-publish')) {
const changesets = cfg.release === 'changesets';
lines.push(
'## Releasing',
'',
changesets
? 'Releases are handled by the `Release` workflow: push a changeset (`npx changeset`) and it opens a version PR, then publishes when that PR merges.'
: 'Pushing a `v*` tag triggers the `Publish` workflow, which publishes to npm with provenance.',
'',
'**Publishing needs npm credentials, which a new repository does not have.** Either:',
'',
'- Set up [npm Trusted Publishing](https://docs.npmjs.com/trusted-publishers) (OIDC) — no secret to store or rotate, and the recommended option; or',
'- Add an [npm automation token](https://docs.npmjs.com/creating-and-viewing-access-tokens) as an `NPM_TOKEN` repository secret.',
'',
changesets
? 'Until one of those is in place the `Release` workflow will fail with `ENEEDAUTH` on every push. That is expected on a brand-new repo.'
: 'Until one of those is in place, tag pushes will fail with `ENEEDAUTH`.',
'',
);
}

lines.push('## License', '', cfg.license === 'none' ? 'Unlicensed.' : `${cfg.license}${cfg.author ? ' © ' + cfg.author : ''}`, '');
return lines.join('\n');
}
14 changes: 14 additions & 0 deletions test/scaffold.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { mkdtemp, mkdir, writeFile, readFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { writeProject, existingEntries, dirIsEmptyOrMissing, writeLockfile } from '../src/cli/write.js';
import { generate, fromPreset } from '../src/core/index.js';

const tmp = () => mkdtemp(join(tmpdir(), 'packkit-'));

Expand Down Expand Up @@ -61,3 +62,16 @@ test('without merge every file is written, nested dirs created', async () => {
assert.equal(written.length, 2);
assert.equal(await readFile(join(dir, 'a/b/c.txt'), 'utf8'), 'hi');
});

test('README documents the npm credential requirement when a publish workflow is included', () => {
const out = generate(fromPreset('ts-lib', { name: 'r1' }));
const readme = out.files['README.md'];
assert.match(readme, /## Releasing/);
assert.match(readme, /Trusted Publishing/);
assert.match(readme, /ENEEDAUTH/, 'names the exact error the user will see');
});

test('README omits the releasing section when nothing publishes', () => {
const out = generate(fromPreset('react-app', { name: 'r2' }));
assert.doesNotMatch(out.files['README.md'], /## Releasing/);
});