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
23 changes: 22 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Get a list of all games currently available on Xbox Game Pass (Console, PC or EA
- [Configuration](#configuration)
- [Examples](https://github.com/NikkelM/Game-Pass-API/tree/main/examples)
- [Feedback](#feedback)
- [Disclaimer](#disclaimer)

## Installation

Expand Down Expand Up @@ -50,7 +51,7 @@ game-pass-api
```

By default it reads `./config.json`; pass `--config <path>` to point at a different file.
Run `game-pass-api --help` to see every command.
Run `game-pass-api --help` to see every command, or `game-pass-api run --help` for the full list of flags.

You can also run without a `config.json` at all, building the configuration entirely from flags:

Expand All @@ -59,8 +60,28 @@ game-pass-api --markets US,DE --platforms console,pc --properties productTitle,p
```

Any option you omit uses its default.
Add `--save-config` to also write the assembled configuration to a `config.json` (or `--save-config <path>`) so you can reuse or edit it later.
Nested options such as images, pricing and user ratings are only available through a `config.json` or the wizard.

### Command-line flags

| Flag | Config key | Description |
| --- | --- | --- |
| `-c, --config <path>` | - | Path to a `config.json`. Defaults to `./config.json`. |
| `--from <dir>` | - | Re-format previously-saved `completeGameProperties_*.json` files in `<dir>` instead of fetching (needs an earlier run with `keepCompleteProperties`). |
| `-o, --out <dir>` | `outputDirectory` | Directory to write output files to. Default `output`. |
| `--markets <codes>` | `markets` | Comma-separated market codes to fetch, e.g. `US,DE`. Enables flag-driven mode. |
| `--platforms <list>` | `platformsToFetch` | Comma-separated platforms: `console,pc,eaPlay`. |
| `--language <code>` | `language` | Language/locale for game properties, e.g. `en-us`. |
| `--format <format>` | `outputFormat` | Output format: `array`, `productTitle`, `productId` or `0-indexed`. |
| `--properties <list>` | `includedProperties` | Comma-separated properties to include: `productTitle,productId,developerName,publisherName,categories,storePage`. |
| `--keep-complete` | `keepCompleteProperties` | Also keep the complete, unfiltered API response per platform and market. |
| `--no-treat-empty-as-null` | `treatEmptyStringsAsNull` | Keep empty strings instead of converting them to `null`. |
| `--save-config [path]` | - | Also write the assembled configuration to a file for reuse. Default `config.json`. |

Nested options (image types, pricing, user ratings, descriptions, release dates) are only available through a `config.json` or the wizard.
The `init` command takes `-o, --output <path>` to choose where the wizard writes the configuration file (default `config.json`).

> Configuration files are validated against a JSON schema (`config.schema.json`, shipped with the package).
> Add `"$schema": "config.schema.json"` to your `config.json`, with a copy of the schema next to it, and your editor will flag mistakes as you type.

Expand Down
6 changes: 5 additions & 1 deletion bin/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import path from 'path';
import { fileURLToPath } from 'url';
import { Command } from 'commander';

import { loadConfig, validateConfig } from '../js/utils.js';
import { loadConfig, validateConfig, saveConfigToFile } from '../js/utils.js';
import { run } from '../js/gamePass.js';
import { runWizard } from '../js/wizard.js';
import { buildConfig, usedBuildingFlags } from '../js/cliConfig.js';
Expand Down Expand Up @@ -33,12 +33,16 @@ program
.option('--properties <list>', 'comma-separated properties to include: productTitle,productId,developerName,publisherName,categories,storePage (flag-driven mode)')
.option('--keep-complete', 'also keep the complete, unfiltered API response per platform and market (flag-driven mode)')
.option('--no-treat-empty-as-null', 'keep empty strings instead of converting them to null (flag-driven mode)')
.option('--save-config [path]', 'also write the assembled configuration to a file for reuse (default: config.json; flag-driven mode)')
.addHelpText('after', '\nProvide a config.json (in the current directory or via --config), or build one from flags with --markets/--platforms/--properties etc. (unspecified options use their defaults).\nRun "game-pass-api init" to create a config interactively, or see the README and config.schema.json for every option.')
.action(async (options, command) => {
// Build the config entirely from flags when config-building flags are used (and no explicit --config file)
if (!options.config && usedBuildingFlags(command)) {
const config = buildConfig(options);
validateConfig(config);
if (options.saveConfig) {
await saveConfigToFile(config, options.saveConfig === true ? 'config.json' : options.saveConfig);
}
await run(config, { fromDirectory: options.from });
return;
}
Expand Down
24 changes: 24 additions & 0 deletions js/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,36 @@ import jsonschema from 'jsonschema';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { confirm } from '@inquirer/prompts';

// The package root, so the shipped config schema is found no matter the working directory
const packageDir = path.dirname(path.dirname(fileURLToPath(import.meta.url)));

export let CONFIG;

// ----- Saving a config -----

// Write a flag-built config to disk for reuse, stripping any secret fields so they are never persisted
// Prompts before overwriting an existing file in an interactive shell; refuses to overwrite non-interactively
export async function saveConfigToFile(config, outputPath, secretFields = []) {
const toWrite = { ...config };
for (const field of secretFields) {
delete toWrite[field];
}
if (fs.existsSync(outputPath)) {
if (!process.stdin.isTTY) {
throw new Error(`"${outputPath}" already exists - remove it, or pass --save-config <path> with a different path.`);
}
const overwrite = await confirm({ message: `"${outputPath}" already exists. Overwrite it?`, default: false });
if (!overwrite) {
console.log('The existing configuration file was not changed.');
return;
}
}
fs.writeFileSync(outputPath, JSON.stringify(toWrite, null, 2));
console.log(`Wrote configuration to "${outputPath}".`);
}

// ----- Config -----

// Load a config from the given path, or discover ./config.json in the current directory, then validate it against the shipped schema
Expand Down