@helloao/cli 0.0.6 → 0.0.8

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,125 +1,132 @@
1
- ## Hello AO CLI
2
-
3
- A Command Line Interface (CLI) that makes it easy to generate and manage your own [Free Use Bible API](https://bible.helloao.org/).
4
-
5
- Additionally, it includes many functions and utilities that can make working with commonly formatted Bible data much easier.
6
-
7
- ### Features
8
-
9
- - Supports [USFM](https://ubsicap.github.io/usfm/), [USX](https://ubsicap.github.io/usx/), and Codex (A JSON format).
10
- - Download over 1000 Bible translations from [fetch.bible](https://fetch.bible/).
11
- - Import Bible translations into a SQLite database.
12
- - Upload to S3, a zip file, or a local directory.
13
-
14
- ### Usage
15
-
16
- ```
17
- Usage: helloao [options] [command]
18
-
19
- A CLI for managing a Free Use Bible API.
20
-
21
- Options:
22
- -V, --version output the version number
23
- -h, --help display help for command
24
-
25
- Commands:
26
- init [options] [path] Initialize a new Bible API DB.
27
- import-translation [options] <dir> [dirs...] Imports a translation from the given directory into the database.
28
- import-translations [options] <dir> Imports all translations from the given directory into the database.
29
- generate-translation-files [options] <input> <dir> Generates API files from the given input translation.
30
- generate-translations-files [options] <input> <dir> Generates API files from the given input translations.
31
- upload-api-files [options] <dest> Uploads API files to the specified destination. For S3, use the format s3://bucket-name/path/to/folder.
32
- fetch-translations [options] <dir> [translations...] Fetches the specified translations from fetch.bible and places them in the given directory.
33
- fetch-audio [options] <dir> [translations...] Fetches the specified audio translations and places them in the given directory.
34
- Translations should be in the format "translationId/audioId". e.g. "BSB/gilbert"
35
- fetch-bible-metadata <dir> Fetches the Theographic bible metadata and places it in the given directory.
36
- help [command] display help for command
37
- ```
38
-
39
- The `@helloao/cli` package can also be used as a library.
40
-
41
- The library exports a variety of actions, utilities, and supporting classes designed to assist with generating and managing a Free Use Bible API.
42
-
43
- There are 6 main exports:
44
-
45
- - `actions` - This export contains function versions of the CLI commands. They make it easy to call a CLI command from a script.
46
- - `db` - This export contains functions that make working with a database easier. It supports operations like importing translations into a database, inserting chapters, verses, etc. and getting an updated database instance from a path.
47
- - `downloads` - This export contains functions that make downloading files easier.
48
- - `files` - This export contains functions that make working with files easier. It has functions to load files from a translation, discover translation metadata from the filesystem, and classes that support uploading API files to the local file system or to a zip archive.
49
- - `uploads` - This export contains functions that make it easy to upload an API to a destination like S3, the local filesystem, or a zip archive.
50
- - `s3` - This export contains a class that can upload files to S3.
51
-
52
- Here are some common operations that you might want to perform:
53
-
54
- #### Get a SQL Database
55
-
56
- ```typescript
57
- import { db } from '@helloao/cli';
58
-
59
- const pathToDb = './bible-database.db';
60
- const database = await db.getDb(pathToDb);
61
-
62
- // do work on the database
63
-
64
- // Close it when you are done.
65
- database.close();
66
- ```
67
-
68
- #### Import a translation into a database from a directory
69
-
70
- ```typescript
71
- import { db } from '@helloao/cli';
72
-
73
- const pathToDb = './bible-database.db';
74
- const database = await db.getDb(pathToDb);
75
-
76
- // Get a DOMParser for parsing USX.
77
- // On Node.js, you may have to import jsdom or linkedom.
78
- const parser = new DOMParser();
79
-
80
- const pathToTranslation = './path/to/translation';
81
-
82
- // Whether to overwrite files that already exist in the database.
83
- // The system will automatically determine the hashes of the input files and overwrite changed files if needed, so this is only needed
84
- // when you know that they need to be overwritten.
85
- const overwrite = false;
86
- await db.importTranslations(database, pathToTranslation, parser, overwrite);
87
- ```
88
-
89
- #### Generate an API from a translation
90
-
91
- ```typescript
92
- import { files, uploads } from '@helloao/cli';
93
- import { generation } from '@helloao/tools';
94
- import { toAsyncIterable } from '@helloao/tools/parser/iterators';
95
-
96
- const translationPath = './path/to/translation';
97
- const translationFiles = await files.loadTranslationFiles(translationPath);
98
-
99
- // Used to parse XML
100
- const domParser = new DOMParser();
101
-
102
- // Generate a dataset from the files
103
- // Datasets organize all the files and their content
104
- // by translation, book, chapter, and verse
105
- const dataset = generation.dataset.generateDataset(files, parser);
106
-
107
- // You can optionally specifiy a prefix that should be added to all API
108
- // links
109
- const pathPrefix = '';
110
-
111
- // Generate an API representation from the files
112
- // This adds links between chapters and additional metadata.
113
- const api = generation.api.generateApiForDataset(dataset, {
114
- pathPrefix,
115
- });
116
-
117
- // Generate output files from the API representation.
118
- // This will give us a list of files and file paths that represent
119
- // the entire API.
120
- const outputFiles = generation.api.generateFilesForApi(api);
121
-
122
- // Optionally upload files by using:
123
- // const dest = 's3://my-bucket';
124
- // await uploads.serializeAndUploadDatasets(dest, toAsyncIterable(outputFiles));
125
- ```
1
+ ## Hello AO CLI
2
+
3
+ A Command Line Interface (CLI) that makes it easy to generate and manage your own [Free Use Bible API](https://bible.helloao.org/).
4
+
5
+ Additionally, it includes many functions and utilities that can make working with commonly formatted Bible data much easier.
6
+
7
+ ### Features
8
+
9
+ - Supports [USFM](https://ubsicap.github.io/usfm/), [USX](https://ubsicap.github.io/usx/), and Codex (A JSON format).
10
+ - Download over 1000 Bible translations from [fetch.bible](https://fetch.bible/).
11
+ - Import Bible translations into a SQLite database.
12
+ - Upload to S3, a zip file, or a local directory.
13
+
14
+ ### Usage
15
+
16
+ ```
17
+ Usage: helloao [options] [command]
18
+
19
+ A CLI for managing a Free Use Bible API.
20
+
21
+ Options:
22
+ -V, --version output the version number
23
+ -h, --help display help for command
24
+
25
+ Commands:
26
+ init [options] [path] Initialize a new Bible API DB.
27
+ generate-translation-metadata Generates a metadata file for a translation.
28
+ import-translation [options] <dir> [dirs...] Imports a translation from the given directory into the database.
29
+ import-translations [options] <dir> Imports all translations from the given directory into the database.
30
+ upload-test-translation [options] <input> Uploads a translation to the HelloAO Free Bible API test S3 bucket.
31
+ Requires access to the HelloAO Free Bible API test S3 bucket.
32
+ For inquiries, please contact hello@helloao.org.
33
+ upload-test-translations [options] <input> Uploads all the translations in the given input directory to the HelloAO Free Bible API test S3 bucket.
34
+ Requires access to the HelloAO Free Bible API test S3 bucket.
35
+ For inquiries, please contact hello@helloao.org.
36
+ generate-translation-files [options] <input> <dir> Generates API files from the given input translation.
37
+ generate-translations-files [options] <input> <dir> Generates API files from the given input translations.
38
+ upload-api-files [options] <dest> Uploads API files to the specified destination. For S3, use the format s3://bucket-name/path/to/folder.
39
+ fetch-translations [options] <dir> [translations...] Fetches the specified translations from fetch.bible and places them in the given directory.
40
+ fetch-audio [options] <dir> [translations...] Fetches the specified audio translations and places them in the given directory.
41
+ Translations should be in the format "translationId/audioId". e.g. "BSB/gilbert"
42
+ fetch-bible-metadata <dir> Fetches the Theographic bible metadata and places it in the given directory.
43
+ help [command] display help for command
44
+ ```
45
+
46
+ The `@helloao/cli` package can also be used as a library.
47
+
48
+ The library exports a variety of actions, utilities, and supporting classes designed to assist with generating and managing a Free Use Bible API.
49
+
50
+ There are 6 main exports:
51
+
52
+ - `actions` - This export contains function versions of the CLI commands. They make it easy to call a CLI command from a script.
53
+ - `db` - This export contains functions that make working with a database easier. It supports operations like importing translations into a database, inserting chapters, verses, etc. and getting an updated database instance from a path.
54
+ - `downloads` - This export contains functions that make downloading files easier.
55
+ - `files` - This export contains functions that make working with files easier. It has functions to load files from a translation, discover translation metadata from the filesystem, and classes that support uploading API files to the local file system or to a zip archive.
56
+ - `uploads` - This export contains functions that make it easy to upload an API to a destination like S3, the local filesystem, or a zip archive.
57
+ - `s3` - This export contains a class that can upload files to S3.
58
+
59
+ Here are some common operations that you might want to perform:
60
+
61
+ #### Get a SQL Database
62
+
63
+ ```typescript
64
+ import { db } from '@helloao/cli';
65
+
66
+ const pathToDb = './bible-database.db';
67
+ const database = await db.getDb(pathToDb);
68
+
69
+ // do work on the database
70
+
71
+ // Close it when you are done.
72
+ database.close();
73
+ ```
74
+
75
+ #### Import a translation into a database from a directory
76
+
77
+ ```typescript
78
+ import { db } from '@helloao/cli';
79
+
80
+ const pathToDb = './bible-database.db';
81
+ const database = await db.getDb(pathToDb);
82
+
83
+ // Get a DOMParser for parsing USX.
84
+ // On Node.js, you may have to import jsdom or linkedom.
85
+ const parser = new DOMParser();
86
+
87
+ const pathToTranslation = './path/to/translation';
88
+
89
+ // Whether to overwrite files that already exist in the database.
90
+ // The system will automatically determine the hashes of the input files and overwrite changed files if needed, so this is only needed
91
+ // when you know that they need to be overwritten.
92
+ const overwrite = false;
93
+ await db.importTranslations(database, pathToTranslation, parser, overwrite);
94
+ ```
95
+
96
+ #### Generate an API from a translation
97
+
98
+ ```typescript
99
+ import { files, uploads } from '@helloao/cli';
100
+ import { generation } from '@helloao/tools';
101
+ import { toAsyncIterable } from '@helloao/tools/parser/iterators';
102
+
103
+ const translationPath = './path/to/translation';
104
+ const translationFiles = await files.loadTranslationFiles(translationPath);
105
+
106
+ // Used to parse XML
107
+ const domParser = new DOMParser();
108
+
109
+ // Generate a dataset from the files
110
+ // Datasets organize all the files and their content
111
+ // by translation, book, chapter, and verse
112
+ const dataset = generation.dataset.generateDataset(files, parser);
113
+
114
+ // You can optionally specifiy a prefix that should be added to all API
115
+ // links
116
+ const pathPrefix = '';
117
+
118
+ // Generate an API representation from the files
119
+ // This adds links between chapters and additional metadata.
120
+ const api = generation.api.generateApiForDataset(dataset, {
121
+ pathPrefix,
122
+ });
123
+
124
+ // Generate output files from the API representation.
125
+ // This will give us a list of files and file paths that represent
126
+ // the entire API.
127
+ const outputFiles = generation.api.generateFilesForApi(api);
128
+
129
+ // Optionally upload files by using:
130
+ // const dest = 's3://my-bucket';
131
+ // await uploads.serializeAndUploadDatasets(dest, toAsyncIterable(outputFiles));
132
+ ```
package/actions.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { InputTranslationMetadata } from '@helloao/tools/generation';
1
2
  import { UploadApiFromDatabaseOptions, UploadApiOptions } from './uploads';
2
3
  export interface InitDbOptions {
3
4
  /**
@@ -117,5 +118,9 @@ export declare function uploadTestTranslations(input: string, options: UploadTes
117
118
  * @param input The input directory that the translations are stored in.
118
119
  * @param options The options to use for the upload.
119
120
  */
120
- export declare function uploadTestTranslation(input: string, options: UploadTestTranslationOptions): Promise<UploadTestTranslationResult>;
121
+ export declare function uploadTestTranslation(input: string, options: UploadTestTranslationOptions): Promise<UploadTestTranslationResult | undefined>;
122
+ /**
123
+ * Asks the user for the metadata for the translation.
124
+ */
125
+ export declare function askForMetadata(defaultId?: string): Promise<InputTranslationMetadata>;
121
126
  //# sourceMappingURL=actions.d.ts.map
package/actions.js CHANGED
@@ -35,6 +35,7 @@ exports.generateTranslationsFiles = generateTranslationsFiles;
35
35
  exports.generateTranslationFiles = generateTranslationFiles;
36
36
  exports.uploadTestTranslations = uploadTestTranslations;
37
37
  exports.uploadTestTranslation = uploadTestTranslation;
38
+ exports.askForMetadata = askForMetadata;
38
39
  const node_path_1 = __importStar(require("node:path"));
39
40
  const database = __importStar(require("./db"));
40
41
  const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
@@ -51,6 +52,8 @@ const files_1 = require("./files");
51
52
  const dataset_1 = require("@helloao/tools/generation/dataset");
52
53
  const uploads_1 = require("./uploads");
53
54
  const s3_1 = require("./s3");
55
+ const prompts_1 = require("@inquirer/prompts");
56
+ const all_iso_language_codes_1 = require("all-iso-language-codes");
54
57
  /**
55
58
  * Initializes a new Bible API DB.
56
59
  * @param dbPath The path to the database. If null or empty, then the "bible-api.db" will be used from the current working directory.
@@ -68,46 +71,46 @@ async function initDb(dbPath, options) {
68
71
  const languages = `(${options.language
69
72
  .map((l) => `'${l}'`)
70
73
  .join(', ')})`;
71
- db.exec(`
72
- ATTACH DATABASE "${sourcePath}" AS source;
73
-
74
- CREATE TABLE "_prisma_migrations" AS SELECT * FROM source._prisma_migrations;
75
-
76
- CREATE TABLE "Translation" AS SELECT * FROM source.Translation
77
- WHERE language IN ${languages};
78
-
79
- CREATE TABLE "Book" AS SELECT * FROM source.Book
80
- INNER JOIN source.Translation ON source.Translation.id = source.Book.translationId
81
- WHERE source.Translation.language IN ${languages};
82
-
83
- CREATE TABLE "Chapter" AS SELECT * FROM source.Chapter
84
- INNER JOIN source.Translation ON source.Translation.id = source.Chapter.translationId
85
- WHERE source.Translation.language IN ${languages};
86
-
87
- CREATE TABLE "ChapterVerse" AS SELECT * FROM source.ChapterVerse
88
- INNER JOIN source.Translation ON source.Translation.id = source.ChapterVerse.translationId
89
- WHERE source.Translation.language IN ${languages};
90
-
91
- CREATE TABLE "ChapterFootnote" AS SELECT * FROM source.ChapterFootnote
92
- INNER JOIN source.Translation ON source.Translation.id = source.ChapterFootnote.translationId
93
- WHERE source.Translation.language IN ${languages};
94
-
95
- CREATE TABLE "ChapterAudioUrl" AS SELECT * FROM source.ChapterAudioUrl
96
- INNER JOIN source.Translation ON source.Translation.id = source.ChapterAudioUrl.translationId
97
- WHERE source.Translation.language IN ${languages};
74
+ db.exec(`
75
+ ATTACH DATABASE "${sourcePath}" AS source;
76
+
77
+ CREATE TABLE "_prisma_migrations" AS SELECT * FROM source._prisma_migrations;
78
+
79
+ CREATE TABLE "Translation" AS SELECT * FROM source.Translation
80
+ WHERE language IN ${languages};
81
+
82
+ CREATE TABLE "Book" AS SELECT * FROM source.Book
83
+ INNER JOIN source.Translation ON source.Translation.id = source.Book.translationId
84
+ WHERE source.Translation.language IN ${languages};
85
+
86
+ CREATE TABLE "Chapter" AS SELECT * FROM source.Chapter
87
+ INNER JOIN source.Translation ON source.Translation.id = source.Chapter.translationId
88
+ WHERE source.Translation.language IN ${languages};
89
+
90
+ CREATE TABLE "ChapterVerse" AS SELECT * FROM source.ChapterVerse
91
+ INNER JOIN source.Translation ON source.Translation.id = source.ChapterVerse.translationId
92
+ WHERE source.Translation.language IN ${languages};
93
+
94
+ CREATE TABLE "ChapterFootnote" AS SELECT * FROM source.ChapterFootnote
95
+ INNER JOIN source.Translation ON source.Translation.id = source.ChapterFootnote.translationId
96
+ WHERE source.Translation.language IN ${languages};
97
+
98
+ CREATE TABLE "ChapterAudioUrl" AS SELECT * FROM source.ChapterAudioUrl
99
+ INNER JOIN source.Translation ON source.Translation.id = source.ChapterAudioUrl.translationId
100
+ WHERE source.Translation.language IN ${languages};
98
101
  `);
99
102
  }
100
103
  else {
101
- db.exec(`
102
- ATTACH DATABASE "${sourcePath}" AS source;
103
-
104
- CREATE TABLE "_prisma_migrations" AS SELECT * FROM source._prisma_migrations;
105
- CREATE TABLE "Translation" AS SELECT * FROM source.Translation;
106
- CREATE TABLE "Book" AS SELECT * FROM source.Book;
107
- CREATE TABLE "Chapter" AS SELECT * FROM source.Chapter;
108
- CREATE TABLE "ChapterVerse" AS SELECT * FROM source.ChapterVerse;
109
- CREATE TABLE "ChapterFootnote" AS SELECT * FROM source.ChapterFootnote;
110
- CREATE TABLE "ChapterAudioUrl" AS SELECT * FROM source.ChapterAudioUrl;
104
+ db.exec(`
105
+ ATTACH DATABASE "${sourcePath}" AS source;
106
+
107
+ CREATE TABLE "_prisma_migrations" AS SELECT * FROM source._prisma_migrations;
108
+ CREATE TABLE "Translation" AS SELECT * FROM source.Translation;
109
+ CREATE TABLE "Book" AS SELECT * FROM source.Book;
110
+ CREATE TABLE "Chapter" AS SELECT * FROM source.Chapter;
111
+ CREATE TABLE "ChapterVerse" AS SELECT * FROM source.ChapterVerse;
112
+ CREATE TABLE "ChapterFootnote" AS SELECT * FROM source.ChapterFootnote;
113
+ CREATE TABLE "ChapterAudioUrl" AS SELECT * FROM source.ChapterAudioUrl;
111
114
  `);
112
115
  }
113
116
  console.log('Done.');
@@ -306,7 +309,11 @@ async function generateTranslationFiles(input, dest, options) {
306
309
  globalThis.DOMParser = linkedom_1.DOMParser;
307
310
  globalThis.Element = linkedom_1.Element;
308
311
  globalThis.Node = linkedom_1.Node;
309
- const files = await (0, files_1.loadTranslationFiles)(node_path_1.default.resolve(input));
312
+ const files = await loadTranslationFilesOrAskForMetadata(node_path_1.default.resolve(input));
313
+ if (!files) {
314
+ console.log('No translation files found.');
315
+ return;
316
+ }
310
317
  const dataset = (0, dataset_1.generateDataset)(files, parser);
311
318
  await (0, uploads_1.serializeAndUploadDatasets)(dest, (0, iterators_1.toAsyncIterable)([dataset]), options);
312
319
  }
@@ -354,7 +361,12 @@ async function uploadTestTranslation(input, options) {
354
361
  globalThis.DOMParser = linkedom_1.DOMParser;
355
362
  globalThis.Element = linkedom_1.Element;
356
363
  globalThis.Node = linkedom_1.Node;
357
- const files = await (0, files_1.loadTranslationFiles)(node_path_1.default.resolve(input));
364
+ const inputPath = node_path_1.default.resolve(input);
365
+ const files = await loadTranslationFilesOrAskForMetadata(inputPath);
366
+ if (!files || files.length <= 0) {
367
+ console.log('No translation files found.');
368
+ return;
369
+ }
358
370
  const hash = (0, files_1.hashInputFiles)(files);
359
371
  const dataset = (0, dataset_1.generateDataset)(files, parser);
360
372
  const url = options.s3Url || 's3://ao-bible-api-public-uploads';
@@ -376,3 +388,81 @@ function getUrls(dest) {
376
388
  url: url,
377
389
  };
378
390
  }
391
+ async function loadTranslationFilesOrAskForMetadata(dir) {
392
+ let files = await (0, files_1.loadTranslationFiles)(dir);
393
+ if (!files) {
394
+ console.log(`No metadata found for the translation in ${dir}`);
395
+ const enterMetadata = await (0, prompts_1.confirm)({
396
+ message: 'Do you want to enter the metadata for the translation?',
397
+ });
398
+ if (!enterMetadata) {
399
+ return null;
400
+ }
401
+ const defaultId = (0, node_path_1.basename)(dir);
402
+ const metadata = await askForMetadata(defaultId);
403
+ const saveMetadata = await (0, prompts_1.confirm)({
404
+ message: 'Do you want to save this metadata?',
405
+ });
406
+ if (saveMetadata) {
407
+ await (0, promises_1.writeFile)(node_path_1.default.resolve(dir, 'metadata.json'), JSON.stringify(metadata, null, 2));
408
+ }
409
+ files = await (0, files_1.loadTranslationFiles)(dir);
410
+ }
411
+ return files;
412
+ }
413
+ /**
414
+ * Asks the user for the metadata for the translation.
415
+ */
416
+ async function askForMetadata(defaultId) {
417
+ const id = await (0, prompts_1.input)({
418
+ message: 'Enter the translation ID',
419
+ default: defaultId,
420
+ });
421
+ const language = await (0, prompts_1.input)({
422
+ message: 'Enter the ISO 639 translation language',
423
+ validate: (input) => {
424
+ return (0, all_iso_language_codes_1.isValid)(input) ? true : 'Invalid language code.';
425
+ },
426
+ required: true,
427
+ });
428
+ const direction = await (0, prompts_1.select)({
429
+ message: 'Enter the text direction of the language',
430
+ choices: [
431
+ { name: 'Left-to-right', value: 'ltr' },
432
+ { name: 'Right-to-left', value: 'rtl' },
433
+ ],
434
+ default: 'ltr',
435
+ });
436
+ const shortName = await (0, prompts_1.input)({
437
+ message: 'Enter the short name of the translation',
438
+ default: id,
439
+ required: false,
440
+ });
441
+ const name = await (0, prompts_1.input)({
442
+ message: 'Enter the name of the translation',
443
+ required: true,
444
+ });
445
+ const englishName = await (0, prompts_1.input)({
446
+ message: 'Enter the English name of the translation',
447
+ default: name,
448
+ });
449
+ const licenseUrl = await (0, prompts_1.input)({
450
+ message: 'Enter the license URL for the translation',
451
+ required: true,
452
+ });
453
+ const website = await (0, prompts_1.input)({
454
+ message: 'Enter the website URL for the translation',
455
+ required: true,
456
+ default: licenseUrl,
457
+ });
458
+ return {
459
+ id,
460
+ language,
461
+ direction: direction,
462
+ shortName,
463
+ name,
464
+ englishName,
465
+ licenseUrl,
466
+ website,
467
+ };
468
+ }
package/cli.js CHANGED
@@ -1,11 +1,31 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
- var __importDefault = (this && this.__importDefault) || function (mod) {
4
- return (mod && mod.__esModule) ? mod : { "default": mod };
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || function (mod) {
20
+ if (mod && mod.__esModule) return mod;
21
+ var result = {};
22
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
23
+ __setModuleDefault(result, mod);
24
+ return result;
5
25
  };
6
26
  Object.defineProperty(exports, "__esModule", { value: true });
7
27
  const commander_1 = require("commander");
8
- const path_1 = __importDefault(require("path"));
28
+ const path_1 = __importStar(require("path"));
9
29
  const promises_1 = require("fs/promises");
10
30
  const linkedom_1 = require("linkedom");
11
31
  const downloads_1 = require("./downloads");
@@ -31,6 +51,32 @@ async function start() {
31
51
  .action(async (dbPath, options) => {
32
52
  await (0, actions_1.initDb)(dbPath, options);
33
53
  });
54
+ program
55
+ .command('generate-translation-metadata')
56
+ .description('Generates a metadata file for a translation.')
57
+ .action(async () => {
58
+ const meta = await (0, actions_1.askForMetadata)();
59
+ console.log('Your metadata:', meta);
60
+ const save = await (0, prompts_1.confirm)({
61
+ message: 'Do you want to save this metadata?',
62
+ });
63
+ if (save) {
64
+ let location = await (0, prompts_1.input)({
65
+ message: 'Where would you like to save the metadata?',
66
+ });
67
+ const ext = (0, path_1.extname)(location);
68
+ if (!ext) {
69
+ if (!location.endsWith('/')) {
70
+ location += '/';
71
+ }
72
+ location += 'metadata.json';
73
+ }
74
+ console.log('Saving metadata to:', location);
75
+ const dir = path_1.default.dirname(location);
76
+ await (0, promises_1.mkdir)(dir, { recursive: true });
77
+ await (0, promises_1.writeFile)(location, JSON.stringify(meta, null, 2));
78
+ }
79
+ });
34
80
  program
35
81
  .command('import-translation <dir> [dirs...]')
36
82
  .description('Imports a translation from the given directory into the database.')
@@ -69,11 +115,13 @@ async function start() {
69
115
  return;
70
116
  }
71
117
  const result = await (0, actions_1.uploadTestTranslation)(input, options);
72
- console.log('\n');
73
- console.log('Version: ', result.version);
74
- console.log('Uploaded to: ', result.uploadS3Url);
75
- console.log('URL: ', result.url);
76
- console.log('Available Translations:', result.availableTranslationsUrl);
118
+ if (result) {
119
+ console.log('\n');
120
+ console.log('Version: ', result.version);
121
+ console.log('Uploaded to: ', result.uploadS3Url);
122
+ console.log('URL: ', result.url);
123
+ console.log('Available Translations:', result.availableTranslationsUrl);
124
+ }
77
125
  });
78
126
  program
79
127
  .command('upload-test-translations <input>')
package/db.js CHANGED
@@ -83,7 +83,7 @@ async function importTranslationBatch(db, dirs, parser, overwrite) {
83
83
  const promises = [];
84
84
  for (let dir of dirs) {
85
85
  const fullPath = path_1.default.resolve(dir);
86
- promises.push((0, files_1.loadTranslationFiles)(fullPath));
86
+ promises.push((0, files_1.loadTranslationFiles)(fullPath).then((files) => files ?? []));
87
87
  }
88
88
  const allFiles = await Promise.all(promises);
89
89
  const files = allFiles.flat();
@@ -130,22 +130,22 @@ function getChangedOrNewInputFiles(db, files) {
130
130
  });
131
131
  }
132
132
  function insertFileMetadata(db, files) {
133
- const fileUpsert = db.prepare(`INSERT INTO InputFile(
134
- translationId,
135
- name,
136
- format,
137
- sha256,
138
- sizeInBytes
139
- ) VALUES (
140
- @translationId,
141
- @name,
142
- @format,
143
- @sha256,
144
- @sizeInBytes
145
- ) ON CONFLICT(translationId, name) DO
146
- UPDATE SET
147
- format=excluded.format,
148
- sha256=excluded.sha256,
133
+ const fileUpsert = db.prepare(`INSERT INTO InputFile(
134
+ translationId,
135
+ name,
136
+ format,
137
+ sha256,
138
+ sizeInBytes
139
+ ) VALUES (
140
+ @translationId,
141
+ @name,
142
+ @format,
143
+ @sha256,
144
+ @sizeInBytes
145
+ ) ON CONFLICT(translationId, name) DO
146
+ UPDATE SET
147
+ format=excluded.format,
148
+ sha256=excluded.sha256,
149
149
  sizeInBytes=excluded.sizeInBytes;`);
150
150
  const insertManyFiles = db.transaction((files) => {
151
151
  for (let file of files) {
@@ -161,32 +161,32 @@ function insertFileMetadata(db, files) {
161
161
  insertManyFiles(files);
162
162
  }
163
163
  function insertTranslations(db, translations) {
164
- const translationUpsert = db.prepare(`INSERT INTO Translation(
165
- id,
166
- name,
167
- language,
168
- shortName,
169
- textDirection,
170
- licenseUrl,
171
- website,
172
- englishName
173
- ) VALUES (
174
- @id,
175
- @name,
176
- @language,
177
- @shortName,
178
- @textDirection,
179
- @licenseUrl,
180
- @website,
181
- @englishName
182
- ) ON CONFLICT(id) DO
183
- UPDATE SET
184
- name=excluded.name,
185
- language=excluded.language,
186
- shortName=excluded.shortName,
187
- textDirection=excluded.textDirection,
188
- licenseUrl=excluded.licenseUrl,
189
- website=excluded.website,
164
+ const translationUpsert = db.prepare(`INSERT INTO Translation(
165
+ id,
166
+ name,
167
+ language,
168
+ shortName,
169
+ textDirection,
170
+ licenseUrl,
171
+ website,
172
+ englishName
173
+ ) VALUES (
174
+ @id,
175
+ @name,
176
+ @language,
177
+ @shortName,
178
+ @textDirection,
179
+ @licenseUrl,
180
+ @website,
181
+ @englishName
182
+ ) ON CONFLICT(id) DO
183
+ UPDATE SET
184
+ name=excluded.name,
185
+ language=excluded.language,
186
+ shortName=excluded.shortName,
187
+ textDirection=excluded.textDirection,
188
+ licenseUrl=excluded.licenseUrl,
189
+ website=excluded.website,
190
190
  englishName=excluded.englishName;`);
191
191
  const insertManyTranslations = db.transaction((translations) => {
192
192
  for (let translation of translations) {
@@ -208,27 +208,27 @@ function insertTranslations(db, translations) {
208
208
  }
209
209
  }
210
210
  function insertTranslationBooks(db, translation, translationBooks) {
211
- const bookUpsert = db.prepare(`INSERT INTO Book(
212
- id,
213
- translationId,
214
- title,
215
- name,
216
- commonName,
217
- numberOfChapters,
218
- \`order\`
219
- ) VALUES (
220
- @id,
221
- @translationId,
222
- @title,
223
- @name,
224
- @commonName,
225
- @numberOfChapters,
226
- @bookOrder
227
- ) ON CONFLICT(id,translationId) DO
228
- UPDATE SET
229
- title=excluded.title,
230
- name=excluded.name,
231
- commonName=excluded.commonName,
211
+ const bookUpsert = db.prepare(`INSERT INTO Book(
212
+ id,
213
+ translationId,
214
+ title,
215
+ name,
216
+ commonName,
217
+ numberOfChapters,
218
+ \`order\`
219
+ ) VALUES (
220
+ @id,
221
+ @translationId,
222
+ @title,
223
+ @name,
224
+ @commonName,
225
+ @numberOfChapters,
226
+ @bookOrder
227
+ ) ON CONFLICT(id,translationId) DO
228
+ UPDATE SET
229
+ title=excluded.title,
230
+ name=excluded.name,
231
+ commonName=excluded.commonName,
232
232
  numberOfChapters=excluded.numberOfChapters;`);
233
233
  const insertMany = db.transaction((books) => {
234
234
  for (let book of books) {
@@ -252,69 +252,69 @@ function insertTranslationBooks(db, translation, translationBooks) {
252
252
  }
253
253
  }
254
254
  function insertTranslationContent(db, translation, book, chapters) {
255
- const chapterUpsert = db.prepare(`INSERT INTO Chapter(
256
- translationId,
257
- bookId,
258
- number,
259
- json
260
- ) VALUES (
261
- @translationId,
262
- @bookId,
263
- @number,
264
- @json
265
- ) ON CONFLICT(translationId,bookId,number) DO
266
- UPDATE SET
255
+ const chapterUpsert = db.prepare(`INSERT INTO Chapter(
256
+ translationId,
257
+ bookId,
258
+ number,
259
+ json
260
+ ) VALUES (
261
+ @translationId,
262
+ @bookId,
263
+ @number,
264
+ @json
265
+ ) ON CONFLICT(translationId,bookId,number) DO
266
+ UPDATE SET
267
267
  json=excluded.json;`);
268
- const verseUpsert = db.prepare(`INSERT INTO ChapterVerse(
269
- translationId,
270
- bookId,
271
- chapterNumber,
272
- number,
273
- text,
274
- contentJson
275
- ) VALUES (
276
- @translationId,
277
- @bookId,
278
- @chapterNumber,
279
- @number,
280
- @text,
281
- @contentJson
282
- ) ON CONFLICT(translationId,bookId,chapterNumber,number) DO
283
- UPDATE SET
284
- text=excluded.text,
268
+ const verseUpsert = db.prepare(`INSERT INTO ChapterVerse(
269
+ translationId,
270
+ bookId,
271
+ chapterNumber,
272
+ number,
273
+ text,
274
+ contentJson
275
+ ) VALUES (
276
+ @translationId,
277
+ @bookId,
278
+ @chapterNumber,
279
+ @number,
280
+ @text,
281
+ @contentJson
282
+ ) ON CONFLICT(translationId,bookId,chapterNumber,number) DO
283
+ UPDATE SET
284
+ text=excluded.text,
285
285
  contentJson=excluded.contentJson;`);
286
- const footnoteUpsert = db.prepare(`INSERT INTO ChapterFootnote(
287
- translationId,
288
- bookId,
289
- chapterNumber,
290
- id,
291
- verseNumber,
292
- text
293
- ) VALUES (
294
- @translationId,
295
- @bookId,
296
- @chapterNumber,
297
- @id,
298
- @verseNumber,
299
- @text
300
- ) ON CONFLICT(translationId,bookId,chapterNumber,id) DO
301
- UPDATE SET
302
- verseNumber=excluded.verseNumber,
286
+ const footnoteUpsert = db.prepare(`INSERT INTO ChapterFootnote(
287
+ translationId,
288
+ bookId,
289
+ chapterNumber,
290
+ id,
291
+ verseNumber,
292
+ text
293
+ ) VALUES (
294
+ @translationId,
295
+ @bookId,
296
+ @chapterNumber,
297
+ @id,
298
+ @verseNumber,
299
+ @text
300
+ ) ON CONFLICT(translationId,bookId,chapterNumber,id) DO
301
+ UPDATE SET
302
+ verseNumber=excluded.verseNumber,
303
303
  text=excluded.text;`);
304
- const chapterAudioUpsert = db.prepare(`INSERT INTO ChapterAudioUrl(
305
- translationId,
306
- bookId,
307
- number,
308
- reader,
309
- url
310
- ) VALUES (
311
- @translationId,
312
- @bookId,
313
- @number,
314
- @reader,
315
- @url
316
- ) ON CONFLICT(translationId,bookId,number,reader) DO
317
- UPDATE SET
304
+ const chapterAudioUpsert = db.prepare(`INSERT INTO ChapterAudioUrl(
305
+ translationId,
306
+ bookId,
307
+ number,
308
+ reader,
309
+ url
310
+ ) VALUES (
311
+ @translationId,
312
+ @bookId,
313
+ @number,
314
+ @reader,
315
+ @url
316
+ ) ON CONFLICT(translationId,bookId,number,reader) DO
317
+ UPDATE SET
318
318
  url=excluded.url;`);
319
319
  const insertChaptersAndVerses = db.transaction(() => {
320
320
  for (let chapter of chapters) {
@@ -516,15 +516,15 @@ async function getDbFromDir(dir) {
516
516
  }
517
517
  async function getDb(dbPath) {
518
518
  const db = new better_sqlite3_1.default(dbPath, {});
519
- db.exec(`CREATE TABLE IF NOT EXISTS "_prisma_migrations" (
520
- "id" TEXT PRIMARY KEY NOT NULL,
521
- "checksum" TEXT NOT NULL,
522
- "finished_at" DATETIME,
523
- "migration_name" TEXT NOT NULL,
524
- "logs" TEXT,
525
- "rolled_back_at" DATETIME,
526
- "started_at" DATETIME NOT NULL DEFAULT current_timestamp,
527
- "applied_steps_count" INTEGER UNSIGNED NOT NULL DEFAULT 0
519
+ db.exec(`CREATE TABLE IF NOT EXISTS "_prisma_migrations" (
520
+ "id" TEXT PRIMARY KEY NOT NULL,
521
+ "checksum" TEXT NOT NULL,
522
+ "finished_at" DATETIME,
523
+ "migration_name" TEXT NOT NULL,
524
+ "logs" TEXT,
525
+ "rolled_back_at" DATETIME,
526
+ "started_at" DATETIME NOT NULL DEFAULT current_timestamp,
527
+ "applied_steps_count" INTEGER UNSIGNED NOT NULL DEFAULT 0
528
528
  );`);
529
529
  const migrations = await (0, fs_extra_1.readdir)(migrationsPath);
530
530
  const appliedMigrations = db
package/files.d.ts CHANGED
@@ -65,9 +65,9 @@ export declare function loadTranslationsFiles(dirs: string[]): Promise<InputFile
65
65
  /**
66
66
  * Loads the files for the given translation.
67
67
  * @param translation The directory that the translation exists in.
68
- * @returns
68
+ * @returns The list of files that were loaded, or null if the translation has no metadata.
69
69
  */
70
- export declare function loadTranslationFiles(translation: string): Promise<InputFile[]>;
70
+ export declare function loadTranslationFiles(translation: string): Promise<InputFile[] | null>;
71
71
  export interface CollectionTranslationMetadata {
72
72
  name: {
73
73
  local: string;
package/files.js CHANGED
@@ -152,7 +152,7 @@ async function loadTranslationsFiles(dirs) {
152
152
  const promises = [];
153
153
  for (let dir of dirs) {
154
154
  const fullPath = path.resolve(dir);
155
- promises.push(loadTranslationFiles(fullPath));
155
+ promises.push(loadTranslationFiles(fullPath).then((files) => files ?? []));
156
156
  }
157
157
  const allFiles = await Promise.all(promises);
158
158
  const files = allFiles.flat();
@@ -161,13 +161,13 @@ async function loadTranslationsFiles(dirs) {
161
161
  /**
162
162
  * Loads the files for the given translation.
163
163
  * @param translation The directory that the translation exists in.
164
- * @returns
164
+ * @returns The list of files that were loaded, or null if the translation has no metadata.
165
165
  */
166
166
  async function loadTranslationFiles(translation) {
167
167
  const metadata = await loadTranslationMetadata(translation);
168
168
  if (!metadata) {
169
169
  console.error('Could not load metadata for translation!', translation);
170
- return [];
170
+ return null;
171
171
  }
172
172
  let files = await (0, promises_1.readdir)(translation);
173
173
  let usfmFiles = files.filter((f) => (0, path_1.extname)(f) === '.usfm' ||
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@helloao/cli",
3
- "version": "0.0.6",
3
+ "version": "0.0.8",
4
4
  "description": "A CLI and related tools for managing HelloAO's Free Bible API",
5
5
  "module": "index.js",
6
6
  "types": "index.d.ts",