@rathnasgala/cli 1.1.9 → 1.1.10

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # Gala CLI
2
2
 
3
- Create a publication, write posts, preview them, and publish from your terminal.
3
+ Create a publication, write posts, preview them, and publish - from your terminal.
4
4
 
5
5
  ## Requirements
6
6
 
@@ -39,7 +39,7 @@ On the first run, it opens GitHub's Gala App installation page. Choose the accou
39
39
  finish the installation, and return to the terminal; the same command resumes automatically.
40
40
 
41
41
  If the Gala GitHub App has not been given access to the new repository, it says so and links to the
42
- one page that grants it GitHub has no way for an app to grant itself access, so that click is
42
+ one page that grants it - GitHub has no way for an app to grant itself access, so that click is
43
43
  unavoidable. Everything else is automatic.
44
44
 
45
45
  ## Write
@@ -63,7 +63,7 @@ npx --yes @rathnasgala/cli@latest preview
63
63
  ```
64
64
 
65
65
  Builds the publication and serves it locally, using the exact framework version the repository is
66
- pinned to so what you see is what gets published. The first run installs that tooling, which takes
66
+ pinned to - so what you see is what gets published. The first run installs that tooling, which takes
67
67
  a moment. Stop it with Ctrl-C.
68
68
 
69
69
  ```console
@@ -118,7 +118,7 @@ npx --yes @rathnasgala/cli@latest upgrade
118
118
 
119
119
  Reports on your sign-ins, the publication folder, the publishing workflow, and anything you have
120
120
  written but not sent. Each check either passes, names what is wrong and how to fix it, or says it
121
- could not be determined never one of those disguised as another.
121
+ could not be determined - never one of those disguised as another.
122
122
 
123
123
  ## Commands
124
124
 
@@ -134,13 +134,13 @@ Run any command with `--help`.
134
134
  | `prism` | Manage author-approved reading configurations | `--root`, `--language`, `--depth`, `--intent`, `--modality`, `--file`, `--reason`, `--yes` |
135
135
  | `upgrade` | Inspect and apply a verified managed-theme update | `--root`, `--channel`, `--yes` |
136
136
  | `doctor` | Check a publication and say what is wrong | `--root` |
137
- | `auth` | Sign in to Gala and GitHub | |
137
+ | `auth` | Sign in to Gala and GitHub | - |
138
138
 
139
139
  `auth` is never a prerequisite you have to remember: any command that needs a credential obtains
140
- one. It exists for when you want to do it deliberately a new machine, or a different account.
140
+ one. It exists for when you want to do it deliberately - a new machine, or a different account.
141
141
 
142
142
  Every command prompts for what it needs when run in a terminal, and every prompt has an option that
143
- supplies it instead. With no terminal attached in CI nothing is ever prompted for: a value that
143
+ supplies it instead. With no terminal attached - in CI - nothing is ever prompted for: a value that
144
144
  cannot be worked out is an error naming the option, so an automated run fails immediately rather
145
145
  than waiting for an answer that will not come.
146
146
 
@@ -155,7 +155,7 @@ directory, with private file permissions. Your GitHub sign-in expires after eigh
155
155
  asks you to sign in again rather than quietly using a credential the server will refuse.
156
156
 
157
157
  Git operations use that same sign-in, not whatever credential your machine happens to have
158
- configured so publishing works on a machine where those differ, or where none is configured.
158
+ configured - so publishing works on a machine where those differ, or where none is configured.
159
159
 
160
160
  ## Troubleshooting
161
161
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rathnasgala/cli",
3
- "version": "1.1.9",
3
+ "version": "1.1.10",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "src"
package/src/api/gala.js CHANGED
@@ -3,8 +3,8 @@ import { request, requestJson } from './http.js';
3
3
  /**
4
4
  * The Gala API.
5
5
  *
6
- * Only the calls the six commands make. v0 carried a generated client covering the whole surface
7
- * comments, reactions, admin, moderation none of which a CLI ever touches.
6
+ * Only the calls the six commands make. v0 carried a generated client covering the whole surface -
7
+ * comments, reactions, admin, moderation - none of which a CLI ever touches.
8
8
  */
9
9
  export const DEFAULT_API_BASE_URL = 'https://api.gala67.com';
10
10
 
package/src/api/github.js CHANGED
@@ -5,7 +5,7 @@ import { requestJson } from './http.js';
5
5
  *
6
6
  * The CLI holds a GitHub App user token, not an OAuth App token. That single difference is what
7
7
  * separates this from v0: an App token can list installations, is not blocked by an organisation's
8
- * OAuth App restrictions, and reaches only repositories the App has been given rather than every
8
+ * OAuth App restrictions, and reaches only repositories the App has been given - rather than every
9
9
  * repository the writer can see, which is what `repo` scope meant.
10
10
  */
11
11
  const API = 'https://api.github.com';
@@ -33,7 +33,7 @@ export function githubApi(token) {
33
33
  * Whether a repository has content yet.
34
34
  *
35
35
  * Creating a repository is asynchronous: GitHub answers with a clone URL before the template
36
- * lands. Cloning into that window produces an empty checkout and a missing site.config.yml
36
+ * lands. Cloning into that window produces an empty checkout and a missing site.config.yml -
37
37
  * a confusing error about a file the template certainly contains. `size` is not usable as the
38
38
  * signal; GitHub still reported 0 for a repository that already had commits.
39
39
  */
package/src/api/http.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Every HTTP call the CLI makes, and every failure it reports.
3
3
  *
4
- * v0 threw away the response body at all twenty failure sites `failed with HTTP 403` and nothing
4
+ * v0 threw away the response body at all twenty failure sites - `failed with HTTP 403` and nothing
5
5
  * else. GitHub explains which 403 it is in that body and nowhere else: an organisation's OAuth App
6
6
  * restrictions, a missing permission, a rename and a rate limit all arrive as 403 with a sentence
7
7
  * telling them apart. Diagnosing anything meant guessing between causes the server had already
@@ -67,6 +67,6 @@ function describe(body) {
67
67
  if (parts.length === 0 && typeof body.raw === 'string') parts.push(body.raw);
68
68
  if (typeof body.documentation_url === 'string') parts.push(`See ${body.documentation_url}`);
69
69
 
70
- const detail = parts.join(' ').replace(/\s+/g, ' ').trim();
70
+ const detail = parts.join(' - ').replace(/\s+/g, ' ').trim();
71
71
  return detail.length > MAX_DETAIL ? `${detail.slice(0, MAX_DETAIL)}…` : detail;
72
72
  }
package/src/auth/gala.js CHANGED
@@ -5,7 +5,7 @@ import { credentialPath, forgetCredential, readCredential, writeCredential } fro
5
5
  * The Gala sign-in.
6
6
  *
7
7
  * A stored credential is checked against the server before anything depends on it, because one
8
- * that parses and has not expired can still be one the API refuses and discovering that four
8
+ * that parses and has not expired can still be one the API refuses - and discovering that four
9
9
  * calls later, as an opaque 401 from whichever endpoint got there first, is how a "sign in again"
10
10
  * became a stack trace.
11
11
  */
@@ -26,7 +26,7 @@ export async function githubCredential({
26
26
  if (stored != null) return stored;
27
27
 
28
28
  // No scopes. A GitHub App's permissions are fixed on the app and granted when the writer installs
29
- // it, so there is nothing to negotiate and nothing to warn them about, which is why the broad
29
+ // it, so there is nothing to negotiate - and nothing to warn them about, which is why the broad
30
30
  // "read/write on every repository you can access" notice is gone.
31
31
  const authorization = await post(DEVICE_CODE_URL, { client_id: clientId }, 'GitHub sign-in');
32
32
  terminal.step('Sign in to GitHub');
@@ -37,7 +37,7 @@ export async function githubCredential({
37
37
  await writeCredential(target, {
38
38
  accessToken: token.access_token,
39
39
  // The app expires user tokens after eight hours and issues a refresh token with each. Exchanging
40
- // one needs the app's client secret, which a published CLI cannot hold so it is kept for the
40
+ // one needs the app's client secret, which a published CLI cannot hold - so it is kept for the
41
41
  // API-side refresh, and until that exists an expired credential asks for one sign-in rather
42
42
  // than failing as an unexplained 401 somewhere deeper.
43
43
  ...(token.expires_in ? { expiresAt: new Date(now() + token.expires_in * 1000).toISOString() } : {}),
package/src/auth/store.js CHANGED
@@ -8,7 +8,7 @@ import path from 'node:path';
8
8
  * Two rules matter more than the storage itself, both learned the hard way:
9
9
  *
10
10
  * A credential is only valid if the server still accepts it. v0 checked expiry and nothing else, so
11
- * a token the API had already decided to refuse it stopped issuing a claim these carried was
11
+ * a token the API had already decided to refuse - it stopped issuing a claim these carried - was
12
12
  * handed out for weeks, and every command failed as an unexplained 401 several calls deep.
13
13
  *
14
14
  * A credential whose shape has changed is not upgradable in place. Bumping `schemaVersion` and
@@ -7,7 +7,7 @@ import { spawn } from 'node:child_process';
7
7
  * v0 wrote to stdout from a dozen modules with no shared shape, so a scaffold emitted raw git
8
8
  * output, bare status codes and half-sentences in whatever order they happened to occur. The
9
9
  * failure mode that cost the most was subtler: prompts and browser launches were decided
10
- * separately in each module, so behaviour with no terminal attached varied by code path some
10
+ * separately in each module, so behaviour with no terminal attached varied by code path - some
11
11
  * hung, some crashed, some silently skipped.
12
12
  *
13
13
  * One object, created once, knows whether there is a terminal. Nothing else has to ask.
@@ -50,7 +50,7 @@ export function createTerminal({
50
50
  async ask(question, { fallback } = {}) {
51
51
  if (!interactive) {
52
52
  if (fallback !== undefined) return fallback;
53
- throw new Error(`${question} no terminal to ask; pass it as an option instead`);
53
+ throw new Error(`${question} - no terminal to ask; pass it as an option instead`);
54
54
  }
55
55
  const reader = createInterface({ input, output });
56
56
  try {
@@ -5,7 +5,7 @@ import { githubCredential } from '../auth/github.js';
5
5
  * Signs in to both, and says so.
6
6
  *
7
7
  * Not a prerequisite the writer has to remember: every command that needs a credential obtains one.
8
- * This exists for the times they want to do it deliberately a new machine, a different account,
8
+ * This exists for the times they want to do it deliberately - a new machine, a different account,
9
9
  * or after a token has expired.
10
10
  */
11
11
  export async function auth({ terminal, options }) {
@@ -13,8 +13,8 @@ export async function auth({ terminal, options }) {
13
13
  const github = await githubCredential({ terminal });
14
14
 
15
15
  terminal.blank();
16
- terminal.done(`Gala valid until ${new Date(gala.expiresAt).toLocaleString()}`);
16
+ terminal.done(`Gala - valid until ${new Date(gala.expiresAt).toLocaleString()}`);
17
17
  terminal.done(github.expiresAt
18
- ? `GitHub valid until ${new Date(github.expiresAt).toLocaleString()}`
19
- : 'GitHub signed in');
18
+ ? `GitHub - valid until ${new Date(github.expiresAt).toLocaleString()}`
19
+ : 'GitHub - signed in');
20
20
  }
@@ -12,7 +12,7 @@ import { createGit } from '../git.js';
12
12
  *
13
13
  * Every check reports one of three things and never guesses between them: it is fine, it is wrong
14
14
  * and here is the fix, or it could not be determined. That third state is the one v0 kept
15
- * collapsing into the second an unreachable GitHub reported as "the App is not installed" sent
15
+ * collapsing into the second - an unreachable GitHub reported as "the App is not installed" sent
16
16
  * writers to install something that was already installed, repeatedly.
17
17
  */
18
18
  export async function doctor({ terminal, options, cwd = process.cwd() }) {
@@ -46,7 +46,7 @@ export async function doctor({ terminal, options, cwd = process.cwd() }) {
46
46
  const source = await readFile(workflow, 'utf8');
47
47
  const siteId = /site-id:\s*([0-9A-Z]{26})/.exec(source)?.[1];
48
48
  return siteId == null
49
- ? wrong('no site id this publication may not be registered', cliCommand('init'))
49
+ ? wrong('no site id - this publication may not be registered', cliCommand('init'))
50
50
  : ok(siteId);
51
51
  }, 'the workflow is missing; registration writes it'));
52
52
 
@@ -61,9 +61,9 @@ export async function doctor({ terminal, options, cwd = process.cwd() }) {
61
61
 
62
62
  terminal.blank();
63
63
  for (const { name, state, detail, fix } of checks) {
64
- if (state === 'ok') terminal.done(`${name} ${detail}`);
65
- else if (state === 'wrong') terminal.fail(`${name} ${detail}`);
66
- else terminal.step(`${name} could not be determined: ${detail}`);
64
+ if (state === 'ok') terminal.done(`${name} - ${detail}`);
65
+ else if (state === 'wrong') terminal.fail(`${name} - ${detail}`);
66
+ else terminal.step(`${name} - could not be determined: ${detail}`);
67
67
  if (fix) terminal.note(fix);
68
68
  }
69
69
 
@@ -22,7 +22,7 @@ import { customDomain } from '../domain.js';
22
22
  * which is why repositories it created never appeared in the web UI.
23
23
  * - The **server** writes `site.config.yml` and the publish workflow during registration. The CLI
24
24
  * used to write its own versions afterwards and commit them, producing a second commit whose
25
- * entire content was rewriting one line and stripping comments and a second workflow run that
25
+ * entire content was rewriting one line and stripping comments - and a second workflow run that
26
26
  * collided with the first one's deployment record and failed.
27
27
  * - **GitHub** turns on Pages by itself once publishing creates a `gh-pages` branch. The CLI used
28
28
  * to poll ten minutes for a run it had caused, then call an API that changed nothing.
@@ -120,7 +120,7 @@ export async function init({ terminal, options, cwd = process.cwd() }) {
120
120
  * Creation is a conversation, not a single call.
121
121
  *
122
122
  * `NEEDS_SHARING` means the repository exists with the right content and the App installation
123
- * simply cannot see it an installation scoped to selected repositories, which is the right way to
123
+ * simply cannot see it - an installation scoped to selected repositories, which is the right way to
124
124
  * have it. That is one grant away from working, and GitHub offers no API to do it on the writer's
125
125
  * behalf: adding a repository to an installation is documented as classic-PAT-only. So it is asked
126
126
  * for, with a link to the one page that grants it.
@@ -152,7 +152,7 @@ async function createPublication({ terminal, api, capability, name, github, inst
152
152
  const owner = result?.owner ?? '';
153
153
  terminal.blank();
154
154
  terminal.step(`${owner}/${result?.name ?? name} exists, but Gala cannot reach it yet`);
155
- terminal.note('its installation covers only selected repositories add this one');
155
+ terminal.note('its installation covers only selected repositories - add this one');
156
156
  terminal.openUrl(result?.recoveryUrl
157
157
  ?? installationUrl(result?.installationId, owner, await viewerOf(github)));
158
158
  if (!await terminal.waitForEnter('Once Gala can access it')) {
@@ -218,7 +218,7 @@ export function installationUrl(installationId, owner, selfLogin) {
218
218
 
219
219
  /**
220
220
  * GitHub answers the creation call before the template content lands. Cloning into that window
221
- * gives an empty checkout and a missing site.config.yml a confusing error about a file the
221
+ * gives an empty checkout and a missing site.config.yml - a confusing error about a file the
222
222
  * template certainly contains.
223
223
  */
224
224
  async function waitForContent(github, owner, name, { attempts = 30, intervalMs = 1000 } = {}) {
@@ -234,7 +234,7 @@ async function publicationName({ terminal, explicitName, directory }) {
234
234
  const answer = proposed ?? await terminal.ask('What should this publication be called?');
235
235
  const name = slugify(answer);
236
236
  if (name == null) {
237
- throw new UsageError(`"${answer}" cannot be a repository name use letters, numbers and hyphens`);
237
+ throw new UsageError(`"${answer}" cannot be a repository name - use letters, numbers and hyphens`);
238
238
  }
239
239
  return name;
240
240
  }
@@ -9,7 +9,7 @@ import { readPublication } from '../publication.js';
9
9
  * Builds the site and serves it locally.
10
10
  *
11
11
  * Eleventy is run from the publication's own `node_modules`, so the preview uses the exact
12
- * framework version the repository is pinned to the same one the publish workflow will use. A
12
+ * framework version the repository is pinned to - the same one the publish workflow will use. A
13
13
  * preview that agrees with the local machine but not with production is worse than no preview.
14
14
  *
15
15
  * That pin is also why this installs dependencies when they are missing. A freshly cloned
@@ -24,21 +24,23 @@ export async function preview({
24
24
  const today = options.value('today');
25
25
 
26
26
  terminal.step('Checking content');
27
- await checkContent({ terminal, root, today, ...(regenerate == null ? {} : { regenerate }) });
27
+ await checkContent({
28
+ terminal, root, today, preview: true, ...(regenerate == null ? {} : { regenerate })
29
+ });
28
30
  terminal.done('Content is valid');
29
31
 
30
32
  const eleventy = path.join(root, 'node_modules', '@11ty', 'eleventy', 'cmd.cjs');
31
33
  if (!await exists(eleventy)) {
32
- terminal.step('Installing what this publication needs first time only');
34
+ terminal.step('Installing what this publication needs - first time only');
33
35
  await install(root, spawnProcess);
34
36
  if (!await exists(eleventy)) {
35
- throw new Error('The preview tooling is still missing after installing. Check package.json.');
37
+ throw new Error('Gala installed the publication dependencies, but the preview tool is still missing. Restore the managed package.json and package-lock.json files, then run preview again.');
36
38
  }
37
39
  terminal.done('Installed');
38
40
  }
39
41
 
40
42
  const publication = await readPublication(root);
41
- terminal.step('Starting the preview stop it with Ctrl-C');
43
+ terminal.step('Starting the preview - stop it with Ctrl-C');
42
44
  if (publication != null) terminal.note(`this is ${publication.name ?? 'your publication'} as it will look`);
43
45
  terminal.blank();
44
46
 
@@ -50,12 +52,17 @@ export async function preview({
50
52
  });
51
53
 
52
54
  return new Promise((resolve, reject) => {
53
- child.once('error', reject);
55
+ child.once('error', (error) => {
56
+ reject(new Error(`The preview could not start: ${error.message}`));
57
+ });
54
58
  child.once('exit', (code, signal) => {
55
59
  // Ctrl-C is how a writer stops a preview; it is not a failure to report.
56
60
  if (signal === 'SIGINT' || signal === 'SIGTERM' || code === 0 || code === 130) resolve();
57
- else if (signal) reject(new Error(`Preview stopped by ${signal}`));
58
- else reject(new Error(`Preview exited with ${code}`));
61
+ else if (signal) {
62
+ reject(new Error(`The preview process was stopped by ${signal}. Review the build output above, correct the reported problem, and run preview again.`));
63
+ } else {
64
+ reject(new Error(`The preview build failed with exit code ${code}. Review the build output above, correct the reported problem, and run preview again.`));
65
+ }
59
66
  });
60
67
  });
61
68
  }
@@ -84,7 +91,7 @@ function install(root, spawnProcess) {
84
91
  resolve();
85
92
  return;
86
93
  }
87
- const failure = new Error('Installing the preview tooling failed');
94
+ const failure = new Error('Gala could not install the preview dependencies. The package-manager output below contains the cause.');
88
95
  failure.detail = said.trim();
89
96
  reject(failure);
90
97
  });
@@ -8,16 +8,15 @@ import { readPublication } from '../publication.js';
8
8
  /**
9
9
  * Validates, records the writer's work, and sends it to GitHub.
10
10
  *
11
- * v0 only sent the writer had to remember to record their changes first, and a run that appeared
11
+ * v0 only sent - the writer had to remember to record their changes first, and a run that appeared
12
12
  * to succeed could ship nothing at all. It also used the machine's git credential rather than the
13
13
  * one the CLI holds, which fails for anyone whose accounts differ.
14
14
  *
15
15
  * The order below is the whole of the difficulty. Every successful publish adds a deployment record
16
16
  * to the branch from the workflow, so the checkout is behind by one before the writer has touched
17
- * anything catching up is the normal condition, not a race fix. It has to happen *before*
18
- * validation, because validation assigns a content id to any post missing one and the workflow
19
- * assigns ids remotely as well; the other order has both sides edit the same file with different
20
- * ids, which git can only report as a conflict.
17
+ * anything - catching up is the normal condition, not a race fix. It has to happen *before*
18
+ * validation, because publish validation assigns a content id to any post still missing one. Local
19
+ * preview is deliberately read-only and can therefore never create a competing ID before this sync.
21
20
  *
22
21
  * Publishing itself happens on GitHub: the workflow in the repository builds and deploys. This
23
22
  * command's job ends when the work is on the branch.
@@ -18,7 +18,7 @@ import { cliCommand } from './cli/invocation.js';
18
18
  * Each was a surface to keep correct and a way to be wrong.
19
19
  *
20
20
  * This lives apart from the dispatcher so the README can be checked against it. v0's README
21
- * outlived its commands by weeks it taught a command that never existed because nothing tied
21
+ * outlived its commands by weeks - it taught a command that never existed - because nothing tied
22
22
  * the two together.
23
23
  *
24
24
  * Each entry carries its own options. Nothing is parsed globally, so an option cannot mean two
package/src/content.js CHANGED
@@ -1,10 +1,20 @@
1
+ import path from 'node:path';
2
+
1
3
  import { regenerateBuildManifest } from '@rathnasgala/content-validation';
2
4
 
5
+ function shownPath(root, file) {
6
+ if (!path.isAbsolute(file)) return file;
7
+ const relative = path.relative(root, file);
8
+ return relative !== '' && !relative.startsWith('..') && !path.isAbsolute(relative)
9
+ ? relative
10
+ : file;
11
+ }
12
+
3
13
  /**
4
14
  * Content validation, shared by preview and publish.
5
15
  *
6
16
  * v0 exposed this as its own `validate` command and then called it from two others, so a writer had
7
- * three ways to learn the same thing and one of them the pre-push hook ran it behind their back.
17
+ * three ways to learn the same thing and one of them - the pre-push hook - ran it behind their back.
8
18
  * Validation is not a task; it is a precondition of showing or shipping. It runs where those happen
9
19
  * and nowhere else.
10
20
  *
@@ -12,20 +22,32 @@ import { regenerateBuildManifest } from '@rathnasgala/content-validation';
12
22
  * theme package installed in the publication's node_modules. That belongs in a test of the
13
23
  * validator, not of how this reports what it found.
14
24
  */
15
- export async function checkContent({ terminal, root, today, regenerate = regenerateBuildManifest }) {
16
- const { results } = await regenerate({ root, today });
25
+ export async function checkContent({
26
+ terminal, root, today, preview = false, regenerate = regenerateBuildManifest
27
+ }) {
28
+ const { results } = await regenerate({ root, today, preview });
17
29
  const failed = results.filter(({ errors }) => errors.length > 0);
18
30
 
19
31
  for (const result of results) {
20
- for (const warning of result.warnings ?? []) terminal.note(`${result.file}: ${warning}`);
32
+ for (const warning of result.warnings ?? []) {
33
+ terminal.note(`${shownPath(root, result.file)} - ${warning}`);
34
+ }
21
35
  }
22
36
  if (failed.length === 0) return results;
23
37
 
24
38
  // Every problem, in one pass. Stopping at the first means fixing one thing, running again, and
25
39
  // only then learning about the next.
26
- terminal.blank();
40
+ const details = [];
27
41
  for (const result of failed) {
28
- for (const error of result.errors) terminal.fail(`${result.file}: ${error}`);
42
+ details.push(shownPath(root, result.file));
43
+ for (const error of result.errors) details.push(` - ${error}`);
29
44
  }
30
- throw new Error(`${failed.length} post${failed.length === 1 ? '' : 's'} cannot be published yet`);
45
+ const problemCount = failed.reduce((count, result) => count + result.errors.length, 0);
46
+ details.push(`Fix ${problemCount === 1 ? 'this problem' : 'these problems'}, then run the command again.`);
47
+ const failure = new Error(
48
+ `Content check failed: ${problemCount} problem${problemCount === 1 ? '' : 's'} `
49
+ + `in ${failed.length} post${failed.length === 1 ? '' : 's'}`
50
+ );
51
+ failure.detail = details.join('\n');
52
+ throw failure;
31
53
  }
package/src/git.js CHANGED
@@ -8,7 +8,7 @@ import path from 'node:path';
8
8
  * which is a different identity from the one the CLI just authenticated with. On the machine where
9
9
  * this surfaced the token belonged to one account and git's stored credential to another, so a
10
10
  * scaffold created the repository through the API and was then refused when it tried to write to
11
- * it. Anyone with no credential configured at all a fresh machine, or SSH-only had no chance.
11
+ * it. Anyone with no credential configured at all - a fresh machine, or SSH-only - had no chance.
12
12
  *
13
13
  * The token travels in the environment, never in the argument list, because arguments are readable
14
14
  * machine-wide through `ps`. Nothing is written to `.git/config`.
@@ -100,11 +100,9 @@ export function createGit({ root, token, spawnProcess = spawn } = {}) {
100
100
  * Brings the remote's commits in, over the top of anything uncommitted.
101
101
  *
102
102
  * `--autostash` matters: this runs before the writer's work is recorded, and a rebase refuses a
103
- * dirty tree. Doing it the other way round record first, then rebase is what produced
104
- * conflicts in `content/posts/*`: validation assigns a content id to any post missing one, and
105
- * the publish workflow assigns one remotely too. Both sides edit the same file, pick different
106
- * ids, and git can only call that a conflict. Taking the remote first means the local pass sees
107
- * ids that already exist and changes nothing.
103
+ * dirty tree. Doing it the other way round - record first, then rebase - lets the publication
104
+ * workflow and the local publish independently update the same file. Taking the remote first
105
+ * makes the post-publish validation pass the sole local writer of any missing content ID.
108
106
  */
109
107
  async takeRemote() {
110
108
  const branch = await git.branch();
@@ -8,8 +8,8 @@ import { parse } from 'yaml';
8
8
  *
9
9
  * The CLI knew every part of this and never said it: `new` printed a file path, `publish` said the
10
10
  * site would appear shortly, and the writer was left to assemble
11
- * `{canonicalBaseUrl}{pathPrefix}/{language}/{slug}/` themselves. That is easy to get wrong the
12
- * language segment is not obvious from anything they typed and being unable to find your own post
11
+ * `{canonicalBaseUrl}{pathPrefix}/{language}/{slug}/` themselves. That is easy to get wrong - the
12
+ * language segment is not obvious from anything they typed - and being unable to find your own post
13
13
  * is a poor first minute with a publishing tool.
14
14
  */
15
15
  export async function readPublication(root) {