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
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,11 @@ job as the example below, resumable, and it is what to reach for if what you
want is the file rather than the code:

```bash
npx themeparks-backfill --list disney # find your park. No key needed.
# How far back it reaches is your plan, so set the key first: without one you get
# the 7 days anonymous access allows, and the run still succeeds, quietly.
export THEMEPARKS_API_KEY=tpw_your_key

npx themeparks-backfill --list disney # find your park. This part needs no key.
npx themeparks-backfill "magic kingdom" # NDJSON, into the current directory
npx themeparks-backfill "Walt Disney World Resort" --format csv --out ./data
```
Expand Down
21 changes: 21 additions & 0 deletions src/backfill.ts
Original file line number Diff line number Diff line change
Expand Up @@ -866,6 +866,10 @@ options:
--version print the package version

examples:
export THEMEPARKS_API_KEY=tpw_your_key
how far back this reaches is your plan, so without a key you get the 7 days
anonymous access allows — and the run still succeeds, quietly.

themeparks-backfill --list disney
find an id, or check a spelling. Destinations with their parks indented
underneath. Works before you have a key.
Expand Down Expand Up @@ -1070,13 +1074,30 @@ export async function main(
if (status === EX_TEMPFAIL) return status;
if (status !== 0) failed.push(park.name);
}
// SAID AGAIN AT THE END, and this is the point of it. The notice above is
// printed before a run that takes minutes, so it scrolls away, and the last
// thing on screen is `done: 433 rows` -- which for a customer who thought they
// were downloading five years is indistinguishable from success. They paid for
// 400 days, got seven, and the command exited 0.
const anonymousNotice = (): void => {
if (apiKey != null) return;
process.stderr.write(
`\nthat was ANONYMOUS ACCESS: the last 7 days only.\n` +
` a free key reads 30 days, Pro 400, Business the whole archive\n` +
` set THEMEPARKS_API_KEY and run the same command again\n` +
` keys: https://www.themeparks.wiki/profile\n`,
);
};

if (failed.length > 0) {
process.stderr.write(
`\n${String(failed.length)} of ${String(targets.length)} did not finish: ${failed.join(', ')}\n` +
` the rest are written. Run the same command again to retry just these.\n`,
);
anonymousNotice();
return 1;
}
anonymousNotice();
return 0;
}

Expand Down
62 changes: 62 additions & 0 deletions test/unit/backfill.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1264,3 +1264,65 @@ describe('argument handling', () => {
expect(err.join('')).toContain('no park or destination matching');
});
});

describe('an anonymous run says so when it finishes', () => {
// The notice at the START scrolls away under a run that takes minutes, and the
// last thing on screen is `done: 433 rows` — which for a customer who thought
// they were downloading five years is indistinguishable from success. Running
// the documented example without a key gives 433 rows of Magic Kingdom instead
// of ~94,000, and exits 0.
let dir: string;
let err: string[];
beforeEach(() => {
dir = mkdtempSync(join(tmpdir(), 'bf-anon-'));
err = [];
vi.spyOn(process.stderr, 'write').mockImplementation((c) => {
err.push(String(c));
return true;
});
vi.spyOn(process.stdout, 'write').mockImplementation(() => true);
});
afterEach(() => {
vi.restoreAllMocks();
rmSync(dir, { recursive: true, force: true });
});

const server = async () => {
const page = await loadFixture('mk_park_daily_page2.json');
const coverage = await loadFixture('mk_history_coverage.json');
return vi.fn((input: unknown) => {
const url = String(input);
const body = url.includes('/history/coverage')
? coverage
: url.includes('/history/daily')
? page
: DESTINATIONS;
return Promise.resolve(
new Response(JSON.stringify(body), { headers: { 'content-type': 'application/json' } }),
);
});
};

it('says it at the end as well as the start', async () => {
const previous = process.env.THEMEPARKS_API_KEY;
delete process.env.THEMEPARKS_API_KEY;
try {
expect(await main([MK, '--out', dir], { fetch: await server() })).toBe(0);
} finally {
if (previous !== undefined) process.env.THEMEPARKS_API_KEY = previous;
}
const text = err.join('');
expect(text).toContain('ANONYMOUS ACCESS');
// Both ends: before, so it can be acted on, and after, so it is read.
expect(text.split('7 days').length - 1).toBeGreaterThanOrEqual(2);
expect(text.trimEnd().endsWith('keys: https://www.themeparks.wiki/profile')).toBe(true);
});

it('says nothing of the sort when a key was given', async () => {
expect(await main([MK, '--out', dir, '--api-key', 'tpw_test'], { fetch: await server() })).toBe(
0,
);
expect(err.join('')).not.toContain('ANONYMOUS');
expect(err.join('')).not.toContain('no API key');
});
});
Loading