diff --git a/.changeset/cli-out-dirs.md b/.changeset/cli-out-dirs.md new file mode 100644 index 00000000..90275966 --- /dev/null +++ b/.changeset/cli-out-dirs.md @@ -0,0 +1,5 @@ +--- +'@stats-forge/github-stats-forge-cli': patch +--- + +fix: create the folders `--out` and a saved card's path name, rather than failing on a folder that is not there yet diff --git a/apps/docs/src/content/docs/docs/usage/cli.md b/apps/docs/src/content/docs/docs/usage/cli.md index 7045cd12..09ef0e6a 100644 --- a/apps/docs/src/content/docs/docs/usage/cli.md +++ b/apps/docs/src/content/docs/docs/usage/cli.md @@ -51,7 +51,7 @@ the prompt, which hides what you type. | Flag | What it does | | ------------------- | --------------------------------------------------------------------------------------------------------------- | | `-c, --card ` | Skip the first prompt: `stats`, `top-langs`, `pin`, `org`, `org-activity`, `contributed-to`, `gist`, `wakatime` | -| `-o, --out ` | Where to write the SVG; the default is named after the card and its subject | +| `-o, --out ` | Where to write the SVG, creating its folders; the default is named after the card and its subject | | `--config ` | A saved card to load, and where "Save these options" writes | | `--options ` | Options as a query string, layered over `--config` | | `-g, --generate` | Render what `--config` holds and exit, without opening the menu | diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index d8e7aeae..ca2f43b0 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -1,7 +1,7 @@ #!/usr/bin/env node import { existsSync } from 'node:fs'; -import { readFile, writeFile } from 'node:fs/promises'; -import { relative, resolve } from 'node:path'; +import { mkdir, readFile, writeFile } from 'node:fs/promises'; +import { dirname, relative, resolve } from 'node:path'; import { formatWithOptions, parseArgs } from 'node:util'; import type { CardKind } from '@stats-forge/github-stats-forge-catalog'; @@ -149,6 +149,7 @@ const renderAndWrite = async ( } const file = outFile(card, query, out); + await mkdir(dirname(file), { recursive: true }); await writeFile(file, rendered.content, 'utf8'); const written = relative(process.cwd(), file); process.stdout.write(`Wrote ${written}\n`); diff --git a/packages/cli/src/saved-card.ts b/packages/cli/src/saved-card.ts index 789c8361..42262aeb 100644 --- a/packages/cli/src/saved-card.ts +++ b/packages/cli/src/saved-card.ts @@ -8,8 +8,8 @@ */ import { existsSync } from 'node:fs'; -import { readFile, writeFile } from 'node:fs/promises'; -import { resolve } from 'node:path'; +import { mkdir, readFile, writeFile } from 'node:fs/promises'; +import { dirname, resolve } from 'node:path'; import type { CardKind } from '@stats-forge/github-stats-forge-catalog'; import { @@ -105,6 +105,7 @@ export const writeSavedCard = async ( options: Record, ): Promise => { const file = resolve(process.cwd(), path); + await mkdir(dirname(file), { recursive: true }); await writeFile(file, savedCardText(card, options), 'utf8'); return file; }; diff --git a/packages/cli/tests/index.spec.ts b/packages/cli/tests/index.spec.ts index 41f67a68..bde09c92 100644 --- a/packages/cli/tests/index.spec.ts +++ b/packages/cli/tests/index.spec.ts @@ -227,6 +227,16 @@ describe('the command line', () => { expect(prompts.navigateOptions).not.toHaveBeenCalled(); }); + it("creates --out's folders when they are not there yet", async () => { + render.mockResolvedValue(DRAWN); + const out = join(dir, 'cards', 'dark', 'card.svg'); + + const result = await run('--config', savedGist, '--generate', '--pat', 'ghp_x', '--out', out); + + await expect(readFile(out, 'utf8')).resolves.toBe('the card'); + expect(result.code).toBeUndefined(); + }); + it('reports a render that failed, writes nothing, and exits 1', async () => { render.mockResolvedValue(REFUSED); const out = join(dir, 'card.svg'); diff --git a/packages/cli/tests/saved-card.spec.ts b/packages/cli/tests/saved-card.spec.ts index c7731dd0..8965722a 100644 --- a/packages/cli/tests/saved-card.spec.ts +++ b/packages/cli/tests/saved-card.spec.ts @@ -51,6 +51,14 @@ describe(writeSavedCard, () => { show_icons: 'true', }); }); + + it('creates the folders the path names', async () => { + const file = join(dir(), 'cards', 'card.json'); + + await writeSavedCard(file, card('stats'), { username: 'anuraghazra' }); + + await expect(readFile(file, 'utf8')).resolves.toContain('"username": "anuraghazra"'); + }); }); describe(readSavedCard, () => {