aiiinotate 0.10.3 → 0.12.0

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
@@ -8,7 +8,7 @@ NOTE: currently, only annotations following the IIIF presentation API 2.0 and 2.
8
8
 
9
9
  ## API
10
10
 
11
- See the [docs on the aiiinotate API](https://github.com/Aikon-platform/aiiinotate/blob/dev/docs/endpoints.md).
11
+ See the [docs on the aiiinotate API](https://github.com/Aikon-platform/aiiinotate/blob/dev/docs/api.md).
12
12
 
13
13
  ---
14
14
 
@@ -16,11 +16,9 @@ See the [docs on the aiiinotate API](https://github.com/Aikon-platform/aiiinotat
16
16
 
17
17
  ### Install
18
18
 
19
- 1. **Install mongodb**.
20
- - see [dev installation script for help](./scripts/setup_mongodb.sh)
21
- - checkout the [official installation guide](https://www.mongodb.com/docs/manual/tutorial/install-mongodb-on-ubuntu/#std-label-install-mdb-community-ubuntu)
19
+ 1. **install mongodb** (see [dev installation script for help](./scripts/setup_mongodb.sh) and checkout the [official installation guide](https://www.mongodb.com/docs/manual/tutorial/install-mongodb-on-ubuntu/#std-label-install-mdb-community-ubuntu))
22
20
 
23
- 2. **Install aiiinotate**
21
+ 2. **install aiiinotate**
24
22
  ```bash
25
23
  npm install aiiinotate
26
24
  ```
@@ -33,37 +31,32 @@ Copy [`config/.env.template`](./config/.env.template) to `.env` and edit it.
33
31
 
34
32
  #### Runtime env sourcing
35
33
 
36
- Once the package is installed, it must access variables from the .env file. However, running `aiiinotate <commands>` creates a subscript which means **you can't `source` an `.env` file**.
34
+ `aiiinotate` runs in a subproicess and won't inherit variables from a plain bash `source` call. Use either of these instead:
35
+
36
+ 1. **`dotenvx` (recommended)**:
37
37
 
38
38
  ```bash
39
- # THIS WILL FAIL: `aiiinotate` is executed in a subscript that doesn't inherit from the variables fetched in `source`.
40
- source /path/to/.env && aiiinotate <command>
39
+ npx dotenvx run -f /path/to/.env -- aiiinotate <command>
41
40
  ```
42
41
 
43
- #### The solutions: do either:
44
- 1. use `dotenvx` to inject variables:
45
- ```bash
46
- npx dotenvx run -f /path/to/.env -- aiiinotate <command>
47
- ```
48
- 2. manually export variables:
49
- ```bash
50
- set -a
51
- source /path/to/.env
52
- set +a
53
- aiiinotate <command>
54
- ```
42
+ 2. **manual export**:
43
+
44
+ ```bash
45
+ set -a && source /path/to/.env && set +a
46
+ aiiinotate <command>
47
+ ```
55
48
 
56
49
  For clarity, we omit env sourcing from the below commands.
57
50
 
58
51
  ### Setup the app
59
52
 
60
- 1. **Start `mongod`**
53
+ 1. **start `mongod`**
61
54
 
62
55
  ```bash
63
56
  sudo systemctl start mongod
64
57
  ```
65
58
 
66
- 2. **Create and configure the database**
59
+ 2. **create and configure the database**
67
60
 
68
61
  ```bash
69
62
  aiiinotate migrate apply
@@ -89,12 +82,20 @@ aiiinotate -- <command>
89
82
 
90
83
  It will give full access to the CLI interface of Aiiinotate. Run `aiiinotate --help` for more info.
91
84
 
85
+ Use the CLI to:
86
+ - import data
87
+ - export data
88
+ - apply and manage migrations
89
+
92
90
  For more information, see [the CLI docs](https://github.com/Aikon-platform/aiiinotate/blob/main/docs/cli.md).
93
91
 
94
- #### Import data
92
+ ---
93
+
94
+ ## DOCKER USAGE
95
95
 
96
- See [the CLI docs](https://github.com/Aikon-platform/aiiinotate/blob/main/docs/cli.md).
96
+ See the docs [here](https://github.com/Aikon-platform/aiiinotate/blob/dev/docs/docker.md)
97
97
 
98
+ For a Mirador integration, see [the reference implementation](github.com/paulhectork/mirador-aiiinotate/tree/main) (aiiinotate + MongoDB + Mirador 4 + MAE bundled in a single `docker-compose`)
98
99
 
99
100
  ---
100
101
 
@@ -123,15 +124,15 @@ npm i
123
124
 
124
125
  After installing, some setup must be done
125
126
 
126
- 1. **Setup your `.env`** file after [`config/.env.template`](./config/.env.template) and place it at `./config/.env`.
127
+ 1. **setup your `.env`** file after [`config/.env.template`](./config/.env.template) and place it at `./config/.env`.
127
128
 
128
- 2. **Start `mongod`**
129
+ 2. **start `mongod`**
129
130
 
130
131
  ```bash
131
132
  sudo systemctl start mongod
132
133
  ```
133
134
 
134
- 3. **Configure the database**
135
+ 3. **configure the database**
135
136
 
136
137
  ```bash
137
138
  npm run migrate apply
@@ -141,40 +142,33 @@ npm run migrate apply
141
142
 
142
143
  Remember to have your `mongodb` service running: `sudo systemctl start mongod` !
143
144
 
144
- - **Start the app**
145
+ #### Start the app
145
146
 
146
147
  ```bash
147
148
  # reload enabled
148
149
  npm run dev
149
150
  ```
150
151
 
151
- - **Test the app**. NOTE: the tests will probably fail if you set the env variable `AIIINOTATE_STRICT_MODE` to `true`.
152
+ #### Test the app
153
+
154
+ Note that the tests will probably fail if you set the env variable `AIIINOTATE_STRICT_MODE` to `true`.
152
155
 
153
156
  ```bash
154
157
  npm run test
155
158
  ```
156
159
 
157
- - **Run the CLI**. (see [the CLI docs](https://github.com/Aikon-platform/aiiinotate/blob/dev/docs/cli.md) for more info)
160
+ #### Run the CLI
158
161
 
162
+ See [the CLI docs](https://github.com/Aikon-platform/aiiinotate/blob/dev/docs/cli.md) for more info:
159
163
 
160
164
  ```bash
161
165
  npm run cli
162
166
  ```
163
167
 
164
- - **Process migrations**. (see [the CLI docs](https://github.com/Aikon-platform/aiiinotate/blob/dev/docs/cli.md) for more info)
165
-
166
- ```bash
167
- # NOTE: the `--` is necessary !
168
- npm run migrate -- <command> <arguments?>
169
- ```
170
-
171
-
172
- - **Import data**. (see [the CLI docs](https://github.com/Aikon-platform/aiiinotate/blob/main/docs/cli.md) for more info)
173
-
174
- ```bash
175
- # NOTE: the `--` is necessary !
176
- npm run cli -- import <arguments>
177
- ```
168
+ Use the CLI to:
169
+ - import data
170
+ - export data
171
+ - apply and manage database migrations
178
172
 
179
173
  ---
180
174
 
package/cli/export.js ADDED
@@ -0,0 +1,118 @@
1
+ import path from "node:path";
2
+
3
+ import { Command, Option, Argument } from "commander";
4
+
5
+ import loadMongoClient from "#cli/utils/mongoClient.js";
6
+ import { dirOk, getCwd, writeCursorToJson } from "#cli/utils/io.js";
7
+
8
+ /** @typedef {import("#types").MongoDbType} MongoDbType */
9
+ /** @typedef {import("#types").MongoFindCursorType} MongoFindCursorType */
10
+ /** @typedef {import("fs").PathLike} PathLike */
11
+
12
+
13
+ const exportableCollections = [ "all", "annotations2", "annotations3", "manifests2", "manifests3" ];
14
+ const actualCollections = exportableCollections.filter(c => c!=="all");
15
+
16
+ /** @type {(outDir: string) => string} */
17
+ const getOutDir = (outDir) => {
18
+ let success;
19
+ if (outDir) {
20
+ [ outDir, success ] = dirOk(outDir);
21
+ if (!success) {
22
+ console.error(`Error opening directory "${outDir}". Are you sure it exists ?`);
23
+ process.exit(1);
24
+ }
25
+ } else {
26
+ outDir = getCwd();
27
+ }
28
+ return outDir;
29
+ }
30
+
31
+ /** @type {() => (collectionName: string) => string|PathLike} */
32
+ const outFileNameFunc = () => {
33
+ // we currify this function so that, if a user exports all collections,
34
+ // output file nmaes share the same timestamp.
35
+ const timestamp = (new Date).toISOString().slice(0,-2);
36
+ return (outDir, collectionName) =>
37
+ path.join(outDir, `${timestamp}-aiiinotate-${collectionName}.json`);
38
+ }
39
+ const outFileName = outFileNameFunc();
40
+
41
+ /**
42
+ * if `count`, returns a function to count all documents in a collection.
43
+ * else, returns a function to find all documents in a collection.
44
+ * @type {(db:MongoClient) => (count: boolean) => (collectionName: string) => MongoFindCursorType|Promise<number>}
45
+ */
46
+ const exportCmdFunc = (db) =>
47
+ (count) =>
48
+ (collectionName) => {
49
+ const collection = db.collection(collectionName);
50
+ return count
51
+ ? collection.countDocuments()
52
+ : collection.find().project({ _id: 0 })
53
+
54
+ }
55
+
56
+ /**
57
+ * export a single collection to a file
58
+ * @param {MongoClient} db
59
+ * @param {string} outDir
60
+ * @returns {(collctionName: string) => Promise<void>}
61
+ */
62
+ const exportCollectionFunc = (db, outDir) => {
63
+ const exportCmd = exportCmdFunc(db)(false);
64
+ const countFunc = exportCmdFunc(db)(true);
65
+
66
+ return async (collectionName) => {
67
+ const out = outFileName(outDir, collectionName);
68
+ const collectionCursor = exportCmd(collectionName);
69
+ const totalCount = await countFunc(collectionName);
70
+ try {
71
+ await writeCursorToJson(out, collectionCursor, totalCount);
72
+ console.log(`Wrote export of collection "${collectionName}" (${totalCount} documents) to "${out}"\n`)
73
+ } catch(e) {
74
+ console.error(`Error writing export of collection "${collectionName}" to "${out}": ${e}`);
75
+ process.exit(1);
76
+ }
77
+ }
78
+ }
79
+
80
+ /**
81
+ * @param {string} collection
82
+ * @param {object} options
83
+ */
84
+ async function action(collection, options) {
85
+ const { client, db } = loadMongoClient();
86
+ const outDir = getOutDir(options.output);
87
+ const collectionNameArray =collection === "all" ? actualCollections : [ collection ];
88
+ const exportCollection = exportCollectionFunc(db, outDir);
89
+
90
+ console.log(`\nExporting collection(s): ${collectionNameArray}\n`)
91
+
92
+ // fetch and write to file
93
+ for (const collectionName of collectionNameArray) {
94
+ await exportCollection(collectionName);
95
+ }
96
+
97
+ console.log(`\nFinished exporting collection(s): ${collectionNameArray} to "${outDir}".`)
98
+ await client.close();
99
+ process.exit(0);
100
+ }
101
+
102
+ /** define the cli */
103
+ function makeExportCommand() {
104
+
105
+ const collectionArg =
106
+ new Argument("collection", `collection to export import: one of ${exportableCollections}`)
107
+ .choices(exportableCollections);
108
+
109
+ const outOpt = new Option("-o, --output <directory>", "Output directory (defaults to current working directory");
110
+
111
+ return new Command("export")
112
+ .description("export aiiinotate data")
113
+ .addArgument(collectionArg)
114
+ .addOption(outOpt)
115
+ .action(async (collection, options, command) => await action(collection, options))
116
+ }
117
+
118
+ export default makeExportCommand;
package/cli/index.js CHANGED
@@ -14,8 +14,10 @@ import { Command, Option } from "commander";
14
14
  // import dotenvx from "dotenvx";
15
15
 
16
16
  import makeImportCommand from "#cli/import.js";
17
+ import makeExportCommand from "#cli/export.js";
17
18
  import makeMigrateCommand from "#cli/migrate.js";
18
19
  import makeServeCommand from "#cli/serve.js";
20
+ import makeXywhToIntCommand from "#cli/xywhToInt.js";
19
21
 
20
22
  function makeCli() {
21
23
 
@@ -33,7 +35,9 @@ function makeCli() {
33
35
  .description(desc)
34
36
  .addCommand(makeServeCommand())
35
37
  .addCommand(makeImportCommand())
36
- .addCommand(makeMigrateCommand());
38
+ .addCommand(makeExportCommand())
39
+ .addCommand(makeMigrateCommand())
40
+ .addCommand(makeXywhToIntCommand());
37
41
 
38
42
  cli.parse(process.argv);
39
43
  }
package/cli/utils/io.js CHANGED
@@ -1,8 +1,12 @@
1
1
  import path from "path";
2
2
  import fs from "fs";
3
3
 
4
+ import ProgressBar from "#cli/utils/progressbar.js";
5
+
6
+ /** @typedef {import("fs").WriteStream} WriteStreamType */
4
7
 
5
8
  const cwd = process.cwd(); // directory the script is run from
9
+ const getCwd = () => process.cwd();
6
10
 
7
11
  /**
8
12
  * convert a filepath to absolute if it is relative.
@@ -24,6 +28,17 @@ const fileOk = (f) => {
24
28
  }
25
29
  }
26
30
 
31
+ /** @returns {[PathLike, boolean]} */
32
+ const dirOk = (d) => {
33
+ d = toAbsPath(d);
34
+ try {
35
+ fs.accessSync(d, fs.constants.R_OK); // will throw if there's an error
36
+ return [ d, fs.lstatSync(d).isDirectory() ]
37
+ } catch (e) {
38
+ return [ d, false ]
39
+ }
40
+ }
41
+
27
42
  /**
28
43
  * @param {string} f
29
44
  * @return {string?}
@@ -79,9 +94,132 @@ async function parseImportInputFile(file) {
79
94
  return [ ...new Set(fileArrayValidate(fileArr)) ];
80
95
  }
81
96
 
97
+ const fileWrite = (f, data) => {
98
+ try {
99
+ fs.writeFileSync(f, data, "utf-8");
100
+ } catch (e) {
101
+ throw new Error(`Error writing to file: ${f} because of error: ${e}`);
102
+ }
103
+ }
104
+
105
+ /**
106
+ * NOTE: this will NOT work on collections and huge objects. use streamWriteJson to write from Mongo Cursors.
107
+ * @param {string|import("fs").PathLike} f
108
+ * @param {Array|object} data
109
+ * @returns {void}
110
+ */
111
+ const fileWriteJson = (f, data) => {
112
+ data = JSON.stringify(data)
113
+ fileWrite(f, data);
114
+ }
115
+
116
+ /**
117
+ * wrapper for writer.write(data) that respects writer backpressure
118
+ *
119
+ * we wrap the .write() in a Promise that checks on the rturn of `writer.write`
120
+ * and drains the stream if necessary: if `writer.write()` returns
121
+ * false, the write buffer is full and must be drained by the OS
122
+ * before continuing.
123
+ *
124
+ * this avoids:
125
+ * - writes that are silently dropped or reordered
126
+ * - corrupt/truncated JSON with no error thrown
127
+ *
128
+ * @type {(writer: WriteStreamType) => (data: string) => Promise<void> }
129
+ */
130
+ const writeOrWait = (writer) =>
131
+ (data) =>
132
+ new Promise((resolve, reject) => {
133
+ const ok = writer.write(data, (error) => {
134
+ if (error) return reject(error);
135
+ });
136
+ if (ok) {
137
+ resolve(); // buffer has room, keep going
138
+ } else {
139
+ writer.once("drain", resolve); // buffer full, wait for drain
140
+ }
141
+ });
142
+
143
+ /**
144
+ * WriteStream.write is actually asyncrhonous => for the last line,
145
+ * we need to use `writer.end` to ensure everything has been written to file.
146
+ *
147
+ * @returns {Promise<void>}
148
+ */
149
+ const endStream = (writer, data) =>
150
+ new Promise((resolve, reject) => {
151
+ writer.end(data, (error) => {
152
+ if (error) return reject(error);
153
+ resolve();
154
+ });
155
+ });
156
+
157
+ /**
158
+ * given a file path `fp` and a FindCursor `cursor`,
159
+ * write all documents in `cursor` to a file.
160
+ *
161
+ * if `totalCount` is defined, print a progress bar as well.
162
+ * else, just print the document number and update at each iteration.
163
+ *
164
+ * necessary to use a string when `cursor` stores a lot of documents
165
+ * (instead of a simple file-write):
166
+ * - cursor.toArray() uses TONS of memory and is slow
167
+ * - JSON.stringify(), used to write JSONs to file, has a maximum
168
+ * size for arrays and will crash if stringifying huge arrays.
169
+ *
170
+ * to check that the output is a valid JSON, use:
171
+ * ```bash
172
+ * python3 -mjson.tool path/to/json > /dev/null && echo "ok" || echo "err"
173
+ * ````
174
+ *
175
+ * @param {string|PathLike} fp
176
+ * @param {MongoFindCursorType} cursor
177
+ * @param {number?} totalCount
178
+ * @returns {Promise}
179
+ */
180
+ const writeCursorToJson = async (fp, cursor, totalCount) => {
181
+ const writer = fs.createWriteStream(fp);
182
+ const onceWriter = writeOrWait(writer);
183
+
184
+ let pb;
185
+ if (totalCount) {
186
+ pb = new ProgressBar({ desc: "writing documents to file", total: totalCount });
187
+ } else {
188
+ console.log("");
189
+ }
190
+
191
+ // we are writing an array => manually write the opening "[".
192
+ await onceWriter("[");
193
+ let i = 0
194
+ for await (const doc of cursor) {
195
+ i += 1;
196
+ // if previous items were written to file, write a "," separator.
197
+ if (i!=1) await onceWriter(", ");
198
+ if (pb) {
199
+ pb.update(i);
200
+ } else {
201
+ process.stdout.clearLine(0);
202
+ process.stdout.cursorTo(0);
203
+ process.stdout.write(`writing document #${i} to file`);
204
+ }
205
+ await onceWriter(JSON.stringify(doc));
206
+ }
207
+ // close the array with a "]" and end the stream.
208
+ await endStream(writer, "]");
209
+
210
+ if (!pb && i>0) console.log("");
211
+ return totalCount;
212
+ }
213
+
214
+
82
215
  export {
83
216
  fileRead,
84
217
  fileOk,
218
+ dirOk,
219
+ fileWrite,
220
+ fileWriteJson,
221
+ getCwd,
85
222
  fileArrayValidate,
86
- parseImportInputFile
223
+ parseImportInputFile,
224
+ writeCursorToJson
87
225
  }
@@ -4,13 +4,14 @@ import { MONGODB_CONNSTRING, MONGODB_DB } from "#constants";
4
4
 
5
5
  /**
6
6
  * load a mongo client and connect it to the database. exists if there's an error
7
- * @returns {import("mongodb").MongoClient}
7
+ * @returns {{ client: import("mongodb").MongoClient, db: import("mongodb").Db }}
8
8
  */
9
9
  function loadMongoClient() {
10
10
  try {
11
- const client = new MongoClient(MONGODB_CONNSTRING);
12
- client.db(MONGODB_DB);
13
- return client;
11
+ const
12
+ client = new MongoClient(MONGODB_CONNSTRING),
13
+ db = client.db(MONGODB_DB);
14
+ return { client, db };
14
15
  } catch (err) {
15
16
  console.error(`mongoClient: could not connect to DB because of error ${err.message}`);
16
17
  process.exit(1);
@@ -0,0 +1,99 @@
1
+ import { Command, Option, Argument } from "commander";
2
+
3
+ import loadMongoClient from "#cli/utils/mongoClient.js";
4
+ import ProgressBar from "#cli/utils/progressbar.js";
5
+ import { maybeToArray, xywhToInt } from "#src/utils/utils.js";
6
+
7
+ /** @typedef {import("mongodb").Db} Db */
8
+
9
+ /**
10
+ * bash command to get faulty annotations from a docker aiiinotate instance:
11
+ * ```
12
+ * docker exec docker-mongo-1 mongosh vhs_aiiinotate --eval \
13
+ * "JSON.stringify(db.annotations2.find({ 'on.xywh': { \$type: 'double' } }, { '_id': 0 }).toArray(), null, 2);" \
14
+ * > mogno_export.json
15
+ * ```
16
+ *
17
+ * bash command to import annotations exported above in a local mongo instance:
18
+ * ```
19
+ * mongoimport \
20
+ * --host localhost \
21
+ * --db aiiinotate \
22
+ * --collection annotations2 \
23
+ * --file ./mogno_export.json \
24
+ * --jsonArray
25
+ * ```
26
+ */
27
+
28
+ /**
29
+ *
30
+ * @param {Db} db - the mongo database
31
+ * @param {object} annotation - the annotation to update
32
+ * @param {boolean} dryRun - if `false`, don't update the annotation in database
33
+ */
34
+ async function updateAnnotation(db, annotation, dryRun) {
35
+ let [ annotationTargetArray, converted ] = maybeToArray(annotation.on, true);
36
+
37
+ annotationTargetArray = annotationTargetArray.map((target) => {
38
+ target.xywh = xywhToInt(target.xywh);
39
+ console.log(target.xywh);
40
+ return target;
41
+ });
42
+ annotation.on = converted
43
+ ? annotationTargetArray[0]
44
+ : annotationTargetArray;
45
+
46
+ // console.log("post: ", annotation.on);
47
+ // we need to do 1 update / document, since the @id filter can select 1 document at a time.
48
+ if (!dryRun) {
49
+ await db.collection("annotations2").updateOne(
50
+ { "@id": annotation["@id"] },
51
+ { $set: annotation },
52
+ { upsert: false }
53
+ );
54
+ }
55
+ return
56
+ }
57
+
58
+ // NOTE: annotations have been exported from vhs and imported in local aiiinotate instance.
59
+ async function action(options) {
60
+ const dryRun = options.dryRun || false;
61
+
62
+ const summary = { total: 0, ok: 0, error: 0 };
63
+
64
+ const { client, db } = loadMongoClient();
65
+ const filter = { "on.xywh": { $type: "double" } };
66
+ const toUpdateCount = await db.collection("annotations2").countDocuments(filter);
67
+ const annotationsCursor = db.collection("annotations2").find(filter);
68
+ summary.total = toUpdateCount;
69
+
70
+ const pb = new ProgressBar({ desc: "updating annotations", total: toUpdateCount });
71
+ let i = 0;
72
+ while (await annotationsCursor.hasNext()) {
73
+ i += 1;
74
+ pb.update(i);
75
+ const annotation = await annotationsCursor.next();
76
+ try {
77
+ await updateAnnotation(db, annotation, dryRun);
78
+ summary.ok += 1;
79
+ } catch (e) {
80
+ console.error(e);
81
+ summary.error += 1
82
+ }
83
+ }
84
+
85
+ console.log(`converting annotation.on.xywh to int (dry run=${dryRun}). results:`, summary);
86
+ await client.close()
87
+ process.exit(0);
88
+ }
89
+
90
+ function makeXywhToIntCommand() {
91
+ const dryRunOpt = new Option("-d, --dry-run", "dry run (don't update data)");
92
+
93
+ return new Command("xywh-to-int")
94
+ .description("convert all `db.annotations2.on.xywh` values from float to int")
95
+ .addOption(dryRunOpt)
96
+ .action(async (options, command) => { return await action(options) });
97
+ }
98
+
99
+ export default makeXywhToIntCommand;
package/docker/Dockerfile CHANGED
@@ -5,23 +5,35 @@ FROM node:23.11
5
5
  ARG PORT
6
6
  ENV PORT=${PORT}
7
7
 
8
+ # path to the .env file
9
+ ARG ENV_PATH
10
+ ENV ENV_PATH="$ENV_PATH"
11
+
8
12
  # set up environment
13
+ ENV DIR=/aiiinotate
9
14
  ENV TERM=linux
15
+ ENV NPM_BIN=/aiiinotate/node_modules/.bin
10
16
  SHELL ["/bin/bash", "-c"]
11
17
 
12
18
  # root of the app in the docker container
13
- WORKDIR /aiiinotate
19
+ WORKDIR ${DIR}
14
20
  # copy the docker .env in the docker container
15
- COPY ./docker/.env /aiiinotate/.env
16
- # for debug: install `iproute2` which provides CLI `ss` to monitor active ports
17
- RUN apt update && apt install iproute2 -y
21
+ COPY "$ENV_PATH" ${DIR}/.env
22
+
23
+ # install iproute and curl for debug
24
+ RUN apt-get update
25
+ RUN apt install curl iproute2 -y
26
+
27
+ # create a npm package in $DIR to run aiiinotate from
28
+ RUN npm init -y
18
29
  # install the app as an NPM library. we do a global install as it saves us from issues with `$PATH`.
19
- RUN npm i -g aiiinotate
30
+ RUN npm i aiiinotate
20
31
 
21
32
  # expose the used port
22
- EXPOSE $PORT
33
+ EXPOSE ${PORT}
23
34
 
24
35
  # run the migrations and start the app.
36
+ # ./node_modules/.bin/aiiinotate is to be able to use the `aiiinotate` cli without doing a global install of `aiiinotate`
25
37
  # NOTE migrations must be done in CMD: they need the mongo service to be running.
26
- CMD aiiinotate --env=/aiiinotate/.env -- migrate apply && \
27
- aiiinotate --env=/aiiinotate/.env -- serve prod;
38
+ CMD ${NPM_BIN}/dotenvx run -f ${DIR}/.env -- ${NPM_BIN}/aiiinotate migrate apply && \
39
+ ${NPM_BIN}/dotenvx run -f ${DIR}/.env -- ${NPM_BIN}/aiiinotate serve prod;