@multiplatform.one/cli 2.1.7 → 2.1.9

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.
@@ -1,43 +1,68 @@
1
- var __defProp = Object.defineProperty;
2
- var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
-
4
1
  // src/bin/multiplatformOne.ts
5
2
  import fsSync from "node:fs";
6
3
  import fs from "node:fs/promises";
7
4
  import os from "node:os";
8
5
  import path from "node:path";
9
6
  import { fileURLToPath } from "node:url";
10
- import axios from "axios";
7
+ import {
8
+ formatServiceList,
9
+ projectRoot,
10
+ waitForApi,
11
+ waitForFrappe,
12
+ waitForKeycloak,
13
+ waitForPostgres,
14
+ waitServices
15
+ } from "@multiplatform.one/utils/dev";
11
16
  import { program } from "commander";
12
17
  import dotenv from "dotenv";
13
18
  import { execa } from "execa";
14
19
  import inquirer from "inquirer";
15
20
  import ora from "ora";
16
- import pg from "pg";
17
- var availableBackends = [
18
- "api",
19
- "frappe"
20
- ];
21
+ var availableServices = ["api", "frappe", "solana", "ethereum", "sui"];
22
+ var defaultDotenvPath = path.resolve(projectRoot, ".env");
21
23
  var availablePlatforms = [
22
24
  "electron",
23
25
  "expo",
24
26
  "keycloak",
25
- "next",
27
+ "one",
26
28
  "storybook",
27
- "storybook-expo"
29
+ "storybook-expo",
30
+ "vocs",
31
+ "vscode",
32
+ "webext"
28
33
  ];
29
- process.env.COOKIECUTTER = `sh ${path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../scripts/cookiecutter.sh")}`;
34
+ process.env.COOKIECUTTER = `sh ${path.resolve(
35
+ path.dirname(fileURLToPath(import.meta.url)),
36
+ "../../scripts/cookiecutter.sh"
37
+ )}`;
30
38
  program.name("multiplatform.one");
31
- program.version(JSON.parse(fsSync.readFileSync(path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../package.json"), "utf8"))?.version);
32
- program.command("init").option("-r, --remote <remote>", "the remote to use", "https://gitlab.com/bitspur/multiplatform.one/cookiecutter").option("-c, --checkout <branch>", "branch, tag or commit to checkout", "main").option("-p, --platforms <platforms>", "platforms to keep").option("-b, --backends <backends>", "backends to keep").argument("[name]", "the name of the project", "").description("init multiplatform.one").action(async (name, options) => {
33
- let { backends, platforms } = options;
34
- if ((await execa("git", [
35
- "rev-parse",
36
- "--is-inside-work-tree"
37
- ], {
39
+ program.version(
40
+ JSON.parse(
41
+ fsSync.readFileSync(
42
+ path.resolve(
43
+ path.dirname(fileURLToPath(import.meta.url)),
44
+ "../../package.json"
45
+ ),
46
+ "utf8"
47
+ )
48
+ )?.version
49
+ );
50
+ program.command("init").option(
51
+ "-r, --remote <remote>",
52
+ "the remote to use",
53
+ "https://gitlab.com/bitspur/multiplatform.one/cookiecutter"
54
+ ).option(
55
+ "-c, --checkout <branch>",
56
+ "branch, tag or commit to checkout",
57
+ "main"
58
+ ).option("-p, --platforms <platforms>", "platforms to use").option("-s, --services <services>", "services to use").argument("[name]", "the name of the project", "").description("init multiplatform.one").action(async (name, options) => {
59
+ let { services, platforms } = options;
60
+ if ((await execa("git", ["rev-parse", "--is-inside-work-tree"], {
38
61
  reject: false
39
62
  })).exitCode === 0) {
40
- throw new Error("multiplatform.one cannot be initialized inside a git repository");
63
+ throw new Error(
64
+ "multiplatform.one cannot be initialized inside a git repository"
65
+ );
41
66
  }
42
67
  if (!name) {
43
68
  name = (await inquirer.prompt([
@@ -48,18 +73,16 @@ program.command("init").option("-r, --remote <remote>", "the remote to use", "ht
48
73
  }
49
74
  ])).name;
50
75
  }
51
- if (!backends) {
52
- const backendsResult = (await inquirer.prompt([
76
+ if (!services) {
77
+ const servicesResult = (await inquirer.prompt([
53
78
  {
54
- message: "What backends are you using?",
55
- name: "backends",
79
+ message: "What services are you using?",
80
+ name: "services",
56
81
  type: "checkbox",
57
- choices: availableBackends.map((name2) => ({
58
- name: name2
59
- }))
82
+ choices: availableServices.map((name2) => ({ name: name2 }))
60
83
  }
61
- ])).backends;
62
- backends = backendsResult.join(",");
84
+ ])).services;
85
+ services = servicesResult.join(",");
63
86
  }
64
87
  if (!platforms) {
65
88
  const platformsResult = (await inquirer.prompt([
@@ -67,56 +90,66 @@ program.command("init").option("-r, --remote <remote>", "the remote to use", "ht
67
90
  message: "What platforms are you using?",
68
91
  name: "platforms",
69
92
  type: "checkbox",
70
- choices: availablePlatforms.map((name2) => ({
71
- name: name2
72
- }))
93
+ choices: availablePlatforms.map((name2) => ({ name: name2 }))
73
94
  }
74
95
  ])).platforms;
75
96
  platforms = platformsResult.join(",");
76
97
  }
77
98
  const cookieCutterConfig = {
78
- default_context: {
79
- name,
80
- platforms,
81
- backends
82
- }
99
+ default_context: { name, platforms, services }
83
100
  };
84
- const cookieCutterConfigFile = path.join(await fs.mkdtemp(path.join(os.tmpdir(), "multiplatform-")), "config.json");
101
+ const cookieCutterConfigFile = path.join(
102
+ await fs.mkdtemp(path.join(os.tmpdir(), "multiplatform-")),
103
+ "config.json"
104
+ );
85
105
  try {
86
- await fs.writeFile(cookieCutterConfigFile, JSON.stringify(cookieCutterConfig, null, 2));
87
- await execa("sh", [
88
- path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../scripts/init.sh"),
89
- "--no-input",
90
- "-f",
91
- "--config-file",
106
+ await fs.writeFile(
92
107
  cookieCutterConfigFile,
93
- "--checkout",
94
- options.checkout,
95
- options.remote
96
- ], {
97
- stdio: "inherit"
98
- });
108
+ JSON.stringify(cookieCutterConfig, null, 2)
109
+ );
110
+ await execa(
111
+ "sh",
112
+ [
113
+ path.resolve(
114
+ path.dirname(fileURLToPath(import.meta.url)),
115
+ "../../scripts/init.sh"
116
+ ),
117
+ "--no-input",
118
+ "-f",
119
+ "--config-file",
120
+ cookieCutterConfigFile,
121
+ "--checkout",
122
+ options.checkout,
123
+ options.remote
124
+ ],
125
+ {
126
+ stdio: "inherit"
127
+ }
128
+ );
99
129
  } finally {
100
- await fs.rm(cookieCutterConfigFile, {
101
- recursive: true,
102
- force: true
103
- });
130
+ await fs.rm(cookieCutterConfigFile, { recursive: true, force: true });
104
131
  }
105
132
  });
106
- program.command("update").option("-r, --remote <remote>", "the remote to use", "https://gitlab.com/bitspur/multiplatform.one/cookiecutter").option("-c, --checkout <branch>", "branch, tag or commit to checkout", "main").option("-p, --platforms <platforms>", "platforms to keep").option("-b, --backends <backends>", "backends to keep").description("update multiplatform.one").action(async (options) => {
107
- let { backends, platforms } = options;
108
- if (!backends) {
109
- const backendsResult = await inquirer.prompt([
133
+ program.command("update").option(
134
+ "-r, --remote <remote>",
135
+ "the remote to use",
136
+ "https://gitlab.com/bitspur/multiplatform.one/cookiecutter"
137
+ ).option(
138
+ "-c, --checkout <branch>",
139
+ "branch, tag or commit to checkout",
140
+ "main"
141
+ ).option("-p, --platforms <platforms>", "platforms to keep").option("-s, --services <services>", "services to keep").description("update multiplatform.one").action(async (options) => {
142
+ let { services, platforms } = options;
143
+ if (!services) {
144
+ const servicesResult = await inquirer.prompt([
110
145
  {
111
- message: "What backends are you using?",
112
- name: "backends",
146
+ message: "What services are you using?",
147
+ name: "services",
113
148
  type: "checkbox",
114
- choices: availableBackends.map((name) => ({
115
- name
116
- }))
149
+ choices: availableServices.map((name) => ({ name }))
117
150
  }
118
151
  ]);
119
- backends = backendsResult.backends.join(",");
152
+ services = servicesResult.services.join(",");
120
153
  }
121
154
  if (!platforms) {
122
155
  const platformsResult = await inquirer.prompt([
@@ -124,202 +157,391 @@ program.command("update").option("-r, --remote <remote>", "the remote to use", "
124
157
  message: "What platforms are you using?",
125
158
  name: "platforms",
126
159
  type: "checkbox",
127
- choices: availablePlatforms.map((name) => ({
128
- name
129
- }))
160
+ choices: availablePlatforms.map((name) => ({ name }))
130
161
  }
131
162
  ]);
132
163
  platforms = platformsResult.platforms.join(",");
133
164
  }
134
- if ((await execa("git", [
135
- "rev-parse",
136
- "--is-inside-work-tree"
137
- ], {
165
+ if ((await execa("git", ["rev-parse", "--is-inside-work-tree"], {
138
166
  reject: false
139
167
  })).exitCode !== 0) {
140
- throw new Error("multiplatform.one cannot be updated outside of a git repository");
168
+ throw new Error(
169
+ "multiplatform.one cannot be updated outside of a git repository"
170
+ );
141
171
  }
142
- if ((await execa("git", [
143
- "diff",
144
- "--cached",
145
- "--quiet"
146
- ], {
172
+ if ((await execa("git", ["diff", "--cached", "--quiet"], {
147
173
  reject: false
148
174
  })).exitCode !== 0) {
149
- throw new Error("multiplatform.one cannot be updated with uncommitted changes");
175
+ throw new Error(
176
+ "multiplatform.one cannot be updated with uncommitted changes"
177
+ );
150
178
  }
151
- const projectRoot = (await execa("git", [
152
- "rev-parse",
153
- "--show-toplevel"
154
- ])).stdout.trim();
179
+ const projectRoot2 = (await execa("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
155
180
  let cookieCutterConfig;
156
- if ((await fs.stat(path.resolve(projectRoot, "package.json"))).isFile() && (await fs.stat(path.resolve(projectRoot, "app/package.json"))).isFile() && JSON.parse(await fs.readFile(path.resolve(projectRoot, "app/package.json"), "utf8"))?.dependencies?.["multiplatform.one"]?.length) {
157
- const name = JSON.parse(await fs.readFile(path.resolve(projectRoot, "package.json"), "utf8"))?.name;
181
+ if ((await fs.stat(path.resolve(projectRoot2, "package.json"))).isFile() && (await fs.stat(path.resolve(projectRoot2, "app/package.json"))).isFile() && JSON.parse(
182
+ await fs.readFile(
183
+ path.resolve(projectRoot2, "app/package.json"),
184
+ "utf8"
185
+ )
186
+ )?.dependencies?.["multiplatform.one"]?.length) {
187
+ const name = JSON.parse(
188
+ await fs.readFile(path.resolve(projectRoot2, "package.json"), "utf8")
189
+ )?.name;
158
190
  if (name) {
159
191
  cookieCutterConfig = {
160
- default_context: {
161
- name,
162
- backends,
163
- platforms
164
- }
192
+ default_context: { name, services, platforms }
165
193
  };
166
194
  }
167
195
  }
168
196
  if (!cookieCutterConfig) throw new Error("not a multiplatform.one project");
169
- const cookieCutterConfigFile = path.join(await fs.mkdtemp(path.join(os.tmpdir(), "multiplatform-")), "config.json");
197
+ const cookieCutterConfigFile = path.join(
198
+ await fs.mkdtemp(path.join(os.tmpdir(), "multiplatform-")),
199
+ "config.json"
200
+ );
170
201
  try {
171
- await fs.writeFile(cookieCutterConfigFile, JSON.stringify(cookieCutterConfig, null, 2));
172
- await execa("sh", [
173
- path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../scripts/update.sh"),
174
- "--no-input",
175
- "-f",
176
- "--config-file",
202
+ await fs.writeFile(
177
203
  cookieCutterConfigFile,
178
- "--checkout",
179
- options.checkout,
180
- options.remote
181
- ], {
182
- cwd: projectRoot,
183
- stdio: "inherit"
184
- });
204
+ JSON.stringify(cookieCutterConfig, null, 2)
205
+ );
206
+ await execa(
207
+ "sh",
208
+ [
209
+ path.resolve(
210
+ path.dirname(fileURLToPath(import.meta.url)),
211
+ "../../scripts/update.sh"
212
+ ),
213
+ "--no-input",
214
+ "-f",
215
+ "--config-file",
216
+ cookieCutterConfigFile,
217
+ "--checkout",
218
+ options.checkout,
219
+ options.remote
220
+ ],
221
+ {
222
+ cwd: projectRoot2,
223
+ stdio: "inherit"
224
+ }
225
+ );
185
226
  } finally {
186
- await fs.rm(cookieCutterConfigFile, {
187
- recursive: true,
188
- force: true
189
- });
227
+ await fs.rm(cookieCutterConfigFile, { recursive: true, force: true });
190
228
  }
191
229
  });
192
- var waitServices = [
193
- "api",
194
- "frappe",
195
- "postgres",
196
- "keycloak"
197
- ];
198
- program.command("wait").description("wait for a service to be ready").option("-i, --interval <interval>", "interval to wait for", "1000").option("-t, --timeout <timeout>", "timeout to wait for", "600000").option("-e, --dotenv <dotenv>", "dotenv file path", ".env").argument("<services>", `the services to wait for (${waitServices.join(", ")})`).action(async (servicesString, options) => {
199
- dotenv.config({
200
- path: options.dotenv
201
- });
230
+ program.command("wait").description("wait for a service to be ready").option("-i, --interval <interval>", "interval to wait for", "1000").option("-t, --timeout <timeout>", "timeout to wait for", "600000").option("-e, --dotenv <dotenv>", "dotenv file path", ".env").argument(
231
+ "<services>",
232
+ `the services to wait for (${waitServices.join(", ")})`
233
+ ).action(async (servicesString, options) => {
234
+ dotenv.config({ path: options.dotenv || defaultDotenvPath });
202
235
  const interval = Number.parseInt(options.interval);
203
236
  const timeout = Number.parseInt(options.timeout);
204
237
  const services = servicesString.split(",");
205
- const unreadyServices = [
206
- ...services
238
+ try {
239
+ await waitWithSpinner(services, { interval, timeout });
240
+ } catch (err) {
241
+ process.exit(1);
242
+ }
243
+ });
244
+ program.command("mesh").description("start the mesh server").option("-a, --api", "use api", false).option("-e, --dotenv <dotenv>", "dotenv file path", ".env").option("-f, --frappe", "use frappe", false).option("-i, --interval <interval>", "interval to wait for", "1000").option("-p, --port <port>", "port to run mesh on", "5002").option("-t, --timeout <timeout>", "timeout to wait for", "600000").action(async (options) => {
245
+ dotenv.config({ path: options.dotenv || defaultDotenvPath });
246
+ process.env.UWS_HTTP_MAX_HEADERS_SIZE = "16384";
247
+ if (options.frappe || options.api) {
248
+ process.env.MESH_API = options.api ? "1" : "0";
249
+ process.env.MESH_FRAPPE = options.frappe ? "1" : "0";
250
+ }
251
+ if (!await fs.stat(path.resolve(projectRoot, "app/main.ts")).catch(() => false)) {
252
+ process.env.MESH_APP = "0";
253
+ }
254
+ if (!await fs.stat(path.resolve(projectRoot, "frappe/package.json")).catch(() => false)) {
255
+ process.env.MESH_FRAPPE = "0";
256
+ }
257
+ const services = [
258
+ ...process.env.MESH_API === "1" ? ["api"] : [],
259
+ ...process.env.MESH_FRAPPE === "1" ? ["frappe"] : []
207
260
  ];
208
- const spinner = ora(`waiting for ${formatServiceList(unreadyServices)}`).start();
261
+ if (services.length > 0) {
262
+ const interval = Number.parseInt(options.interval);
263
+ const timeout = Number.parseInt(options.timeout);
264
+ try {
265
+ await waitWithSpinner(services, { interval, timeout });
266
+ } catch (err) {
267
+ process.exit(1);
268
+ }
269
+ }
270
+ await execa(
271
+ "mesh",
272
+ [
273
+ "dev",
274
+ "--port",
275
+ Number(options.port || process.env.MESH_PORT || 5002).toString()
276
+ ],
277
+ {
278
+ stdio: "inherit"
279
+ }
280
+ );
281
+ });
282
+ program.command("build").argument(
283
+ "[args...]",
284
+ `build to run: ${[...availableServices, "packages"].join(", ")} (default: all)`
285
+ ).description("run build command").action(async (args) => {
286
+ args = args.flatMap((arg) => arg.split(","));
287
+ const packages = args.includes("packages");
288
+ const projects = args.filter((arg) => arg !== "packages");
289
+ if (!projects.length && !packages || packages) {
290
+ await execa(
291
+ "turbo",
292
+ [
293
+ "run",
294
+ "build",
295
+ "--filter",
296
+ "'!./platforms/*'",
297
+ "--filter",
298
+ "'!./api'",
299
+ "--filter",
300
+ "'!./solana'",
301
+ "--filter",
302
+ "'!./ethereum'",
303
+ "--filter",
304
+ "'!./sui'"
305
+ ],
306
+ {
307
+ stdio: "inherit",
308
+ shell: true,
309
+ cwd: projectRoot
310
+ }
311
+ );
312
+ }
313
+ for (const platform of await fs.readdir("platforms")) {
314
+ if ((!projects.length && !packages || projects.includes(platform)) && await fs.access(path.join(projectRoot, "platforms", platform, "package.json")).then(
315
+ () => JSON.parse(
316
+ fsSync.readFileSync(
317
+ path.join(projectRoot, "platforms", platform, "package.json"),
318
+ "utf8"
319
+ )
320
+ ).scripts?.build
321
+ ).catch(() => false)) {
322
+ await execa("./mkpm", [`${platform}/build`], {
323
+ stdio: "inherit",
324
+ shell: true,
325
+ cwd: projectRoot
326
+ });
327
+ }
328
+ }
329
+ for (const service of availableServices) {
330
+ if ((!projects.length && !packages || projects.includes(service)) && await fs.access(path.join(projectRoot, service, "package.json")).then(
331
+ () => JSON.parse(
332
+ fsSync.readFileSync(
333
+ path.join(projectRoot, service, "package.json"),
334
+ "utf8"
335
+ )
336
+ ).scripts?.build
337
+ ).catch(() => false)) {
338
+ await execa("./mkpm", [`${service}/build`], {
339
+ stdio: "inherit",
340
+ shell: true,
341
+ cwd: projectRoot
342
+ });
343
+ }
344
+ }
345
+ });
346
+ program.command("test").argument(
347
+ "[args...]",
348
+ `test to run: ${[...availableServices, "packages"].join(", ")} (default: all)`
349
+ ).description("run test command").action(async (args) => {
350
+ args = args.flatMap((arg) => arg.split(","));
351
+ const packages = args.includes("packages");
352
+ const projects = args.filter((arg) => arg !== "packages");
353
+ if (!projects.length && !packages || packages) {
354
+ await execa(
355
+ "turbo",
356
+ [
357
+ "run",
358
+ "test",
359
+ "--filter",
360
+ "'!./platforms/*'",
361
+ "--filter",
362
+ "'!./api'",
363
+ "--filter",
364
+ "'!./solana'",
365
+ "--filter",
366
+ "'!./ethereum'",
367
+ "--filter",
368
+ "'!./sui'"
369
+ ],
370
+ {
371
+ stdio: "inherit",
372
+ shell: true,
373
+ cwd: projectRoot
374
+ }
375
+ );
376
+ }
377
+ for (const platform of await fs.readdir("platforms")) {
378
+ if ((!projects.length && !packages || projects.includes(platform)) && await fs.access(path.join(projectRoot, "platforms", platform, "package.json")).then(
379
+ () => JSON.parse(
380
+ fsSync.readFileSync(
381
+ path.join(projectRoot, "platforms", platform, "package.json"),
382
+ "utf8"
383
+ )
384
+ ).scripts?.test
385
+ ).catch(() => false)) {
386
+ await execa("./mkpm", [`${platform}/test`], {
387
+ stdio: "inherit",
388
+ shell: true,
389
+ cwd: projectRoot
390
+ });
391
+ }
392
+ }
393
+ for (const service of availableServices) {
394
+ if ((!projects.length && !packages || projects.includes(service)) && await fs.access(path.join(projectRoot, service, "package.json")).then(
395
+ () => JSON.parse(
396
+ fsSync.readFileSync(
397
+ path.join(projectRoot, service, "package.json"),
398
+ "utf8"
399
+ )
400
+ ).scripts?.test
401
+ ).catch(() => false)) {
402
+ await execa("./mkpm", [`${service}/test`], {
403
+ stdio: "inherit",
404
+ shell: true,
405
+ cwd: projectRoot
406
+ });
407
+ }
408
+ }
409
+ });
410
+ program.command("check").argument("[args...]", "checks to run: spelling, types, lint (default: all)").description("run check command").action(async (args) => {
411
+ args = args.flatMap((arg) => arg.split(","));
412
+ const spelling = !args.length || args.includes("spelling");
413
+ const types = !args.length || args.includes("types");
414
+ const lint = !args.length || args.includes("lint");
415
+ let exitCode = 0;
416
+ if (spelling) {
417
+ try {
418
+ await execa(
419
+ "cspell",
420
+ [
421
+ "--unique",
422
+ "`(git ls-files && (git lfs ls-files | cut -d' ' -f3))`"
423
+ ],
424
+ {
425
+ stdio: "inherit",
426
+ shell: true,
427
+ cwd: projectRoot
428
+ }
429
+ );
430
+ } catch (err) {
431
+ if (err instanceof Error && "exitCode" in err) {
432
+ exitCode = err.exitCode;
433
+ } else {
434
+ exitCode = 1;
435
+ }
436
+ }
437
+ }
438
+ if (types) {
439
+ try {
440
+ await execa("turbo", ["run", "typecheck"], {
441
+ stdio: "inherit",
442
+ shell: true,
443
+ cwd: projectRoot
444
+ });
445
+ } catch (err) {
446
+ if (err instanceof Error && "exitCode" in err) {
447
+ exitCode = err.exitCode;
448
+ } else {
449
+ exitCode = 1;
450
+ }
451
+ }
452
+ }
453
+ if (lint) {
454
+ try {
455
+ await execa(
456
+ "biome",
457
+ [
458
+ "check",
459
+ "--fix",
460
+ "--unsafe",
461
+ "`(git ls-files && (git lfs ls-files | cut -d' ' -f3)) | sort | uniq -u | grep -E '(html)|(s?css)|(md)|(json)|(yaml)|([jt]sx?)$'`"
462
+ ],
463
+ {
464
+ stdio: "inherit",
465
+ shell: true,
466
+ cwd: projectRoot
467
+ }
468
+ );
469
+ } catch (err) {
470
+ if (err instanceof Error && "exitCode" in err) {
471
+ exitCode = err.exitCode;
472
+ } else {
473
+ exitCode = 1;
474
+ }
475
+ }
476
+ }
477
+ process.exit(exitCode);
478
+ });
479
+ program.command("count").description("count lines of code").action(async () => {
480
+ await execa(
481
+ "cloc",
482
+ [
483
+ "`(git ls-files && (git lfs ls-files | cut -d' ' -f3)) | sort | uniq -u | grep -E '(html)|(s?css)|(md)|(json)|(yaml)|([jt]sx?)$'`"
484
+ ],
485
+ {
486
+ stdio: "inherit",
487
+ shell: true,
488
+ cwd: projectRoot
489
+ }
490
+ );
491
+ });
492
+ program.command("generate").description("run generate command").action(async () => {
493
+ await execa("turbo", ["run", "generate"], {
494
+ stdio: "inherit",
495
+ shell: true,
496
+ cwd: projectRoot
497
+ });
498
+ });
499
+ program.parse(process.argv);
500
+ async function waitWithSpinner(services, options = {}) {
501
+ const { interval = 1e3, timeout = 6e5 } = options;
502
+ const waitFunctions = {
503
+ api: waitForApi,
504
+ frappe: waitForFrappe,
505
+ postgres: waitForPostgres,
506
+ keycloak: waitForKeycloak
507
+ };
508
+ const unreadyServices = [...services];
509
+ const spinner = ora(
510
+ `waiting for ${formatServiceList(unreadyServices)}`
511
+ ).start();
209
512
  function updateSpinner(readyService) {
210
513
  unreadyServices.splice(unreadyServices.indexOf(readyService), 1);
211
- spinner.stop();
212
514
  spinner.succeed(`${readyService} is ready`);
213
- if (!unreadyServices.length) return;
214
- spinner.start(`waiting for ${formatServiceList(unreadyServices)}`);
515
+ if (unreadyServices.length) {
516
+ spinner.start(`waiting for ${formatServiceList(unreadyServices)}`);
517
+ }
215
518
  }
216
- __name(updateSpinner, "updateSpinner");
217
519
  let timeoutId;
218
520
  try {
219
521
  await Promise.race([
220
- Promise.all(services.map(async (service) => {
221
- switch (service) {
222
- case "api": {
223
- await waitForApi(interval);
224
- updateSpinner("api");
225
- return;
226
- }
227
- case "frappe": {
228
- await waitForFrappe(interval);
229
- updateSpinner("frappe");
230
- return;
231
- }
232
- case "postgres": {
233
- await waitForPostgres(interval);
234
- updateSpinner("postgres");
235
- return;
236
- }
237
- case "keycloak": {
238
- await waitForKeycloak(interval);
239
- updateSpinner("keycloak");
240
- return;
241
- }
242
- }
243
- ora(`available services are ${formatServiceList(waitServices)}`).fail();
244
- })),
522
+ Promise.all(
523
+ services.map(async (service) => {
524
+ const waitFn = waitFunctions[service];
525
+ if (!waitFn) throw new Error(`Unknown service: ${service}`);
526
+ await waitFn(interval);
527
+ updateSpinner(service);
528
+ })
529
+ ),
245
530
  new Promise((_, reject) => {
246
531
  timeoutId = setTimeout(() => reject(new Error("Timeout")), timeout);
247
532
  })
248
533
  ]);
249
534
  } catch (err) {
250
- if (err instanceof Error && err.message === "Timeout") {
251
- spinner.fail(`${formatServiceList(unreadyServices)} timed out after ${timeout}ms`);
535
+ const error = err;
536
+ if (error.message === "Timeout") {
537
+ spinner.fail(
538
+ `${formatServiceList(unreadyServices)} timed out after ${timeout}ms`
539
+ );
252
540
  } else {
253
- spinner.fail(err);
541
+ spinner.fail(error.message);
254
542
  }
255
- process.exit(1);
543
+ throw error;
256
544
  } finally {
257
545
  clearTimeout(timeoutId);
258
546
  }
259
- });
260
- function formatServiceList(services) {
261
- if (services.length === 1) return services[0];
262
- if (services.length === 2) return `${services[0]} and ${services[1]}`;
263
- return `${services.slice(0, -1).join(", ")} and ${services[services.length - 1]}`;
264
- }
265
- __name(formatServiceList, "formatServiceList");
266
- program.parse(process.argv);
267
- async function waitForApi(interval) {
268
- try {
269
- const res = await axios.get(`http://localhost:${process.env.API_PORT || "5001"}/healthz`);
270
- if ((res?.status || 500) < 300) {
271
- return;
272
- }
273
- } catch (err) {
274
- }
275
- await new Promise((resolve) => setTimeout(resolve, interval));
276
- return waitForApi(interval);
277
- }
278
- __name(waitForApi, "waitForApi");
279
- async function waitForFrappe(interval) {
280
- try {
281
- const res = await axios.get(`${process.env.FRAPPE_BASE_URL || "http://frappe.localhost"}/api/method/ping`);
282
- if ((res?.status || 500) < 300) {
283
- return;
284
- }
285
- } catch (err) {
286
- }
287
- await new Promise((resolve) => setTimeout(resolve, interval));
288
- return waitForFrappe(interval);
289
- }
290
- __name(waitForFrappe, "waitForFrappe");
291
- async function waitForPostgres(interval) {
292
- const client = new pg.Client({
293
- database: process.env.POSTGRES_DATABASE || "postgres",
294
- host: process.env.POSTGRES_HOSTNAME || "localhost",
295
- password: process.env.POSTGRES_PASSWORD || "postgres",
296
- port: Number.parseInt(process.env.POSTGRES_PORT || "5432"),
297
- user: process.env.POSTGRES_USERNAME || "postgres"
298
- });
299
- try {
300
- await client.connect();
301
- while (true) {
302
- try {
303
- await client.query("SELECT 1");
304
- return;
305
- } catch (error) {
306
- }
307
- await new Promise((resolve) => setTimeout(resolve, interval));
308
- }
309
- } finally {
310
- await client.end();
311
- }
312
- }
313
- __name(waitForPostgres, "waitForPostgres");
314
- async function waitForKeycloak(interval) {
315
- try {
316
- const res = await axios.get(`${process.env.KEYCLOAK_BASE_URL || "http://localhost:8080"}/realms/master/.well-known/openid-configuration`);
317
- if ((res?.status || 500) < 300) {
318
- return;
319
- }
320
- } catch (err) {
321
- }
322
- await new Promise((resolve) => setTimeout(resolve, interval));
323
- return waitForKeycloak(interval);
324
547
  }
325
- __name(waitForKeycloak, "waitForKeycloak");