@spica/cli 0.18.17 → 0.18.18-pre2

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.
Files changed (101) hide show
  1. package/index.js +3516 -38
  2. package/package.json +56 -31
  3. package/index.d.ts +0 -2
  4. package/index.js.map +0 -1
  5. package/src/commands/asset/apply.d.ts +0 -2
  6. package/src/commands/asset/apply.js +0 -56
  7. package/src/commands/asset/apply.js.map +0 -1
  8. package/src/commands/asset/delete.d.ts +0 -2
  9. package/src/commands/asset/delete.js +0 -35
  10. package/src/commands/asset/delete.js.map +0 -1
  11. package/src/commands/bucket/orm.d.ts +0 -45
  12. package/src/commands/bucket/orm.js +0 -478
  13. package/src/commands/bucket/orm.js.map +0 -1
  14. package/src/commands/context/ls.d.ts +0 -2
  15. package/src/commands/context/ls.js +0 -23
  16. package/src/commands/context/ls.js.map +0 -1
  17. package/src/commands/context/remove.d.ts +0 -2
  18. package/src/commands/context/remove.js +0 -16
  19. package/src/commands/context/remove.js.map +0 -1
  20. package/src/commands/context/set.d.ts +0 -2
  21. package/src/commands/context/set.js +0 -39
  22. package/src/commands/context/set.js.map +0 -1
  23. package/src/commands/context/switch.d.ts +0 -2
  24. package/src/commands/context/switch.js +0 -22
  25. package/src/commands/context/switch.js.map +0 -1
  26. package/src/commands/function/orm.d.ts +0 -2
  27. package/src/commands/function/orm.js +0 -63
  28. package/src/commands/function/orm.js.map +0 -1
  29. package/src/commands/project/ls.d.ts +0 -2
  30. package/src/commands/project/ls.js +0 -55
  31. package/src/commands/project/ls.js.map +0 -1
  32. package/src/commands/project/remove.d.ts +0 -2
  33. package/src/commands/project/remove.js +0 -82
  34. package/src/commands/project/remove.js.map +0 -1
  35. package/src/commands/project/start.d.ts +0 -2
  36. package/src/commands/project/start.js +0 -443
  37. package/src/commands/project/start.js.map +0 -1
  38. package/src/commands/project/sync.d.ts +0 -201
  39. package/src/commands/project/sync.js +0 -785
  40. package/src/commands/project/sync.js.map +0 -1
  41. package/src/commands/project/upgrade.d.ts +0 -2
  42. package/src/commands/project/upgrade.js +0 -174
  43. package/src/commands/project/upgrade.js.map +0 -1
  44. package/src/compile.d.ts +0 -22
  45. package/src/compile.js +0 -115
  46. package/src/compile.js.map +0 -1
  47. package/src/config.d.ts +0 -9
  48. package/src/config.js +0 -53
  49. package/src/config.js.map +0 -1
  50. package/src/console.d.ts +0 -14
  51. package/src/console.js +0 -47
  52. package/src/console.js.map +0 -1
  53. package/src/context.d.ts +0 -17
  54. package/src/context.js +0 -54
  55. package/src/context.js.map +0 -1
  56. package/src/function/helpers.d.ts +0 -2
  57. package/src/function/helpers.js +0 -12
  58. package/src/function/helpers.js.map +0 -1
  59. package/src/function/index.d.ts +0 -2
  60. package/src/function/index.js +0 -3
  61. package/src/function/index.js.map +0 -1
  62. package/src/function/interface.d.ts +0 -28
  63. package/src/function/interface.js +0 -3
  64. package/src/function/interface.js.map +0 -1
  65. package/src/function/modifier.d.ts +0 -41
  66. package/src/function/modifier.js +0 -64
  67. package/src/function/modifier.js.map +0 -1
  68. package/src/function/triggers/http/index.d.ts +0 -7
  69. package/src/function/triggers/http/index.js +0 -8
  70. package/src/function/triggers/http/index.js.map +0 -1
  71. package/src/function/triggers/http/services/axios.d.ts +0 -46
  72. package/src/function/triggers/http/services/axios.js +0 -163
  73. package/src/function/triggers/http/services/axios.js.map +0 -1
  74. package/src/function/triggers/http/services/index.d.ts +0 -3
  75. package/src/function/triggers/http/services/index.js +0 -4
  76. package/src/function/triggers/http/services/index.js.map +0 -1
  77. package/src/function/triggers/http/transformer.d.ts +0 -16
  78. package/src/function/triggers/http/transformer.js +0 -49
  79. package/src/function/triggers/http/transformer.js.map +0 -1
  80. package/src/function/triggers/index.d.ts +0 -4
  81. package/src/function/triggers/index.js +0 -6
  82. package/src/function/triggers/index.js.map +0 -1
  83. package/src/http.d.ts +0 -11
  84. package/src/http.js +0 -30
  85. package/src/http.js.map +0 -1
  86. package/src/project.d.ts +0 -11
  87. package/src/project.js +0 -63
  88. package/src/project.js.map +0 -1
  89. package/src/range.d.ts +0 -1
  90. package/src/range.js +0 -13
  91. package/src/range.js.map +0 -1
  92. package/src/representative.d.ts +0 -13
  93. package/src/representative.js +0 -120
  94. package/src/representative.js.map +0 -1
  95. package/src/status.d.ts +0 -19
  96. package/src/status.js +0 -39
  97. package/src/status.js.map +0 -1
  98. package/src/validator.d.ts +0 -7
  99. package/src/validator.js +0 -52
  100. package/src/validator.js.map +0 -1
  101. package/tsconfig.app.tsbuildinfo +0 -1
package/index.js CHANGED
@@ -1,39 +1,3517 @@
1
+ // apps/cli/index.ts
2
+ import caporalCore4 from "@caporal/core";
3
+
4
+ // apps/cli/src/console.ts
5
+ import { blue, bold, red, yellow } from "colorette";
6
+ import columnify from "columnify";
7
+ import ora from "ora";
8
+ import util from "util";
9
+ function spin(options) {
10
+ const spinner = ora({ ...options, color: options.color || "yellow" });
11
+ if (typeof options.op == "function") {
12
+ options.op = options.op(spinner);
13
+ }
14
+ if (options.op instanceof Promise) {
15
+ spinner.start();
16
+ return options.op.then(
17
+ (result) => {
18
+ spinner.succeed();
19
+ return result;
20
+ },
21
+ (error) => {
22
+ spinner.fail();
23
+ if (typeof error == "object" && "message" in error) {
24
+ console.error(error.message);
25
+ console.debug(error);
26
+ process.exit();
27
+ }
28
+ return Promise.reject(error);
29
+ }
30
+ );
31
+ }
32
+ return spinner.start();
33
+ }
34
+ var Logger = class extends console.Console {
35
+ error(message, ...optionalParams) {
36
+ super.error(bold(red(util.format(message, ...optionalParams))));
37
+ }
38
+ warn(message, ...optionalParams) {
39
+ super.warn(bold(yellow(util.format(message, ...optionalParams))));
40
+ }
41
+ info(message, ...optionalParams) {
42
+ super.info(bold(blue(util.format(message, ...optionalParams))));
43
+ }
44
+ table(tabularData, columns) {
45
+ if (!columns) {
46
+ super.log(columnify(tabularData, { columnSplitter: " " }));
47
+ } else {
48
+ super.log(columnify(tabularData, { columns, columnSplitter: " " }));
49
+ }
50
+ }
51
+ };
52
+ global.console = new Logger(process.stdout, process.stderr);
53
+
54
+ // apps/cli/src/http.ts
55
+ import axios from "axios";
56
+
57
+ // apps/cli/src/context.ts
58
+ import fs2 from "fs";
59
+ import os2 from "os";
60
+ import path from "path";
61
+
62
+ // apps/cli/src/config.ts
63
+ import { cosmiconfig } from "cosmiconfig";
64
+ import fs from "fs";
65
+ import os from "os";
66
+ import _path from "path";
67
+ var config;
68
+ ((config2) => {
69
+ const explorer = cosmiconfig("spica", {
70
+ searchPlaces: [
71
+ `.spicarc`,
72
+ `.spicarc.json`,
73
+ `.spicarc.yaml`,
74
+ `.spicarc.yml`,
75
+ `.spicarc.js`,
76
+ `.spicarc.cjs`
77
+ ]
78
+ });
79
+ async function has() {
80
+ const config3 = await get();
81
+ return !!config3.context;
82
+ }
83
+ config2.has = has;
84
+ async function get() {
85
+ let searchResult = await explorer.search(process.cwd());
86
+ if (!searchResult) {
87
+ searchResult = await explorer.search(os.homedir());
88
+ }
89
+ if (!searchResult || !searchResult.config || !searchResult.config.context) {
90
+ throw new Error(
91
+ `No context has been selected.
92
+ $\xA0spica context switch <name> to switch context.`
93
+ );
94
+ }
95
+ return searchResult.config;
96
+ }
97
+ config2.get = get;
98
+ async function path9() {
99
+ let config3 = await explorer.search(process.cwd());
100
+ if (!config3) {
101
+ config3 = await explorer.search(os.homedir());
102
+ }
103
+ if (config3) {
104
+ return config3.filepath;
105
+ }
106
+ return void 0;
107
+ }
108
+ config2.path = path9;
109
+ async function set(config3) {
110
+ let configPath = await path9();
111
+ if (!configPath) {
112
+ configPath = _path.join(os.homedir(), ".spicarc");
113
+ }
114
+ fs.writeFileSync(configPath, JSON.stringify(config3, void 0, 2));
115
+ }
116
+ config2.set = set;
117
+ })(config || (config = {}));
118
+
119
+ // apps/cli/src/context.ts
120
+ var context;
121
+ ((context2) => {
122
+ const ctxPath = path.join(os2.homedir(), ".spicactx");
123
+ function load() {
124
+ if (!fs2.existsSync(ctxPath)) {
125
+ return {};
126
+ }
127
+ return JSON.parse(fs2.readFileSync(ctxPath).toString());
128
+ }
129
+ function write(ctxs) {
130
+ fs2.writeFileSync(ctxPath, JSON.stringify(ctxs));
131
+ }
132
+ function set(name, data) {
133
+ const cxts = load();
134
+ cxts[name] = data;
135
+ write(cxts);
136
+ }
137
+ context2.set = set;
138
+ function has(name) {
139
+ const cxts = load();
140
+ return !!cxts[name];
141
+ }
142
+ context2.has = has;
143
+ function get(name) {
144
+ const cxts = load();
145
+ const ctx = cxts[name];
146
+ if (!ctx) {
147
+ throw new Error(
148
+ `Could not find the context ${name}
149
+ $\xA0spica context set --name="${name}" --apikey="<APIKEY_HERE>" to create this context.`
150
+ );
151
+ }
152
+ return ctx;
153
+ }
154
+ context2.get = get;
155
+ async function getCurrent() {
156
+ const { context: name } = await config.get();
157
+ return get(name);
158
+ }
159
+ context2.getCurrent = getCurrent;
160
+ function list2() {
161
+ const cxts = load();
162
+ return Object.entries(cxts).map(([name, ctx]) => ({ ...ctx, name }));
163
+ }
164
+ context2.list = list2;
165
+ function remove2(name) {
166
+ const cxts = load();
167
+ delete cxts[name];
168
+ write(cxts);
169
+ }
170
+ context2.remove = remove2;
171
+ })(context || (context = {}));
172
+
173
+ // apps/cli/src/http.ts
174
+ var httpService;
175
+ ((httpService2) => {
176
+ function create2({
177
+ baseUrl,
178
+ authorization
179
+ }) {
180
+ const instance = axios.create({
181
+ baseURL: baseUrl
182
+ });
183
+ instance.interceptors.response.use(
184
+ (response) => {
185
+ return response.data;
186
+ },
187
+ (error) => {
188
+ if (!error.response) {
189
+ return Promise.reject(error);
190
+ }
191
+ return Promise.reject(error.response.data || error.response);
192
+ }
193
+ );
194
+ instance.defaults.headers.common["Authorization"] = authorization;
195
+ return instance;
196
+ }
197
+ httpService2.create = create2;
198
+ async function createFromCurrentCtx() {
199
+ const ctx = await context.getCurrent();
200
+ return create2({
201
+ baseUrl: ctx.url,
202
+ authorization: ctx.authorization
203
+ });
204
+ }
205
+ httpService2.createFromCurrentCtx = createFromCurrentCtx;
206
+ })(httpService || (httpService = {}));
207
+
208
+ // apps/cli/src/representative.ts
209
+ import fs3 from "fs";
210
+ import path2 from "path";
211
+ import YAML from "yaml";
212
+ import dotenv from "dotenv";
213
+ var RepresentativeManager = class {
214
+ constructor(cwd) {
215
+ this.cwd = cwd;
216
+ this.serializer = /* @__PURE__ */ new Map();
217
+ this.parsers = /* @__PURE__ */ new Map();
218
+ this.serializer.set("json", (val) => JSON.stringify(val));
219
+ this.parsers.set("json", (val) => JSON.parse(val));
220
+ this.serializer.set("yaml", (val) => {
221
+ const document = new YAML.Document();
222
+ document.contents = val;
223
+ return document.toString();
224
+ });
225
+ this.parsers.set("yaml", (val) => YAML.parse(val));
226
+ this.serializer.set("js", (val) => val);
227
+ this.parsers.set("js", (val) => val);
228
+ this.serializer.set("ts", (val) => val);
229
+ this.parsers.set("ts", (val) => val);
230
+ this.serializer.set("env", (content) => {
231
+ let lines = [];
232
+ for (const [key, value] of Object.entries(content)) {
233
+ lines.push(`${key}=${value}`);
234
+ }
235
+ return lines.join("\n");
236
+ });
237
+ this.parsers.set("env", (content) => {
238
+ return dotenv.parse(content);
239
+ });
240
+ }
241
+ getModuleDir(module) {
242
+ return path2.join(this.cwd, module);
243
+ }
244
+ serializeContent(content, extension) {
245
+ const serializer = this.serializer.get(extension);
246
+ if (!serializer) {
247
+ throw Error(`Unknown extension type ${extension}`);
248
+ }
249
+ return serializer(content);
250
+ }
251
+ parseContent(content, extension) {
252
+ const parser = this.parsers.get(extension);
253
+ if (!parser) {
254
+ throw Error(`Unknown extension type ${extension}`);
255
+ }
256
+ return parser(content);
257
+ }
258
+ write(module, id, fileName, content, extension) {
259
+ const resourcesDirectory = path2.join(this.cwd, module, id);
260
+ if (!fs3.existsSync(resourcesDirectory)) {
261
+ fs3.mkdirSync(resourcesDirectory, { recursive: true });
262
+ }
263
+ const fullPath = path2.join(resourcesDirectory, `${fileName}.${extension}`);
264
+ content = this.serializeContent(content, extension);
265
+ return fs3.promises.writeFile(fullPath, content);
266
+ }
267
+ readResource(module, id, fileNames = []) {
268
+ const moduleDir = this.getModuleDir(module);
269
+ const resourcesPath = path2.join(moduleDir, id);
270
+ const contents = {};
271
+ if (!fs3.existsSync(resourcesPath)) {
272
+ return Promise.resolve(contents);
273
+ }
274
+ let resources = fs3.readdirSync(resourcesPath);
275
+ if (fileNames.length) {
276
+ resources = resources.filter((resource) => fileNames.includes(resource));
277
+ }
278
+ const promises = [];
279
+ for (const resource of resources) {
280
+ const resourcePath = path2.join(resourcesPath, resource);
281
+ const promise = fs3.promises.readFile(resourcePath).then((content) => {
282
+ const extension = resource.split(".").pop();
283
+ const key = resource.replace(`.${extension}`, "");
284
+ const value = this.parseContent(content.toString(), extension);
285
+ contents[key] = value;
286
+ });
287
+ promises.push(promise);
288
+ }
289
+ return Promise.all(promises).then(() => {
290
+ return { _id: id, contents };
291
+ });
292
+ }
293
+ read(module, resNameValidator, fileNameFilter = []) {
294
+ const moduleDir = this.getModuleDir(module);
295
+ let ids;
296
+ if (!fs3.existsSync(moduleDir)) {
297
+ ids = [];
298
+ } else {
299
+ ids = fs3.readdirSync(moduleDir);
300
+ }
301
+ const promises = [];
302
+ const results = [];
303
+ for (const id of ids) {
304
+ if (!resNameValidator(id)) {
305
+ continue;
306
+ }
307
+ const promise = this.readResource(module, id, fileNameFilter).then((resource) => {
308
+ if (resource.contents && Object.keys(resource.contents).length) {
309
+ results.push(resource);
310
+ }
311
+ });
312
+ promises.push(promise);
313
+ }
314
+ return Promise.all(promises).then(() => results);
315
+ }
316
+ rm(module, id) {
317
+ let dir = this.cwd;
318
+ if (module) {
319
+ dir = path2.join(dir, module);
320
+ }
321
+ if (id) {
322
+ dir = path2.join(dir, id);
323
+ }
324
+ return fs3.promises.rm(dir, { recursive: true });
325
+ }
326
+ };
327
+
328
+ // apps/cli/src/commands/asset/apply.ts
329
+ import path3 from "path";
330
+ import fs4 from "fs";
331
+ import YAML2 from "yaml";
332
+ async function apply({ options }) {
333
+ const folderPath = options.path || process.cwd();
334
+ const filename = path3.join(folderPath, "asset.yaml");
335
+ const rawDocument = fs4.readFileSync(filename).toString();
336
+ const assetMeta = YAML2.parseDocument(rawDocument).toJSON();
337
+ console.log("Found Asset:");
338
+ console.log(` ${assetMeta.name}`);
339
+ const repManager = new RepresentativeManager(folderPath);
340
+ const moduleAndFiles = /* @__PURE__ */ new Map();
341
+ moduleAndFiles.set("bucket", ["schema.yaml"]);
342
+ moduleAndFiles.set("function", [
343
+ "schema.yaml",
344
+ "package.json",
345
+ "env.env",
346
+ "index.ts",
347
+ "index.js"
348
+ ]);
349
+ moduleAndFiles.set("preference", ["schema.yaml"]);
350
+ moduleAndFiles.set("dashboard", ["schema.yaml"]);
351
+ moduleAndFiles.set("apikey", ["schema.yaml"]);
352
+ const resourceNameValidator = (id) => id.match(/^([0-9a-fA-F]{24}$)|identity/);
353
+ const resources = [];
354
+ console.log("");
355
+ console.log("Found Resources:");
356
+ for (let [_module, fileNames] of moduleAndFiles.entries()) {
357
+ let resource = await repManager.read(_module, resourceNameValidator, fileNames);
358
+ console.log(` - ${_module}: ${resource.length}`);
359
+ resource = resource.map((r) => {
360
+ r.module = _module;
361
+ return r;
362
+ });
363
+ resources.push(...resource);
364
+ }
365
+ if (options.dryRun) return;
366
+ const body = {
367
+ ...assetMeta,
368
+ resources
369
+ };
370
+ const client = await httpService.createFromCurrentCtx();
371
+ await client.post("/asset", body);
372
+ return console.info(`Asset ${body.name} has been uploaded successfully`);
373
+ }
374
+ function apply_default(program2) {
375
+ return program2.command("asset apply", "Put objects to the API.").option(
376
+ "--path <path>",
377
+ "Path of the folder that container asset.yaml file and resources of it. Current working directory is the default value."
378
+ ).option("--dry-run", "Shows the changes that will be applied to the target instance.").action(apply);
379
+ }
380
+
381
+ // apps/cli/src/commands/asset/delete.ts
382
+ import fs5 from "fs";
383
+ import YAML3 from "yaml";
384
+ import path4 from "path";
385
+ async function _delete({ options }) {
386
+ const type = options.type;
387
+ const folderPath = options.path || process.cwd();
388
+ const filename = path4.join(folderPath, "asset.yaml");
389
+ const rawDocument = fs5.readFileSync(filename).toString();
390
+ const assetMeta = YAML3.parseDocument(rawDocument).toJSON();
391
+ const client = await httpService.createFromCurrentCtx();
392
+ const asset = await client.get("/asset", {
393
+ params: {
394
+ name: assetMeta.name
395
+ }
396
+ }).then((r) => r[0]);
397
+ if (!asset) {
398
+ console.error(`Asset ${assetMeta.name} does not exist`);
399
+ return;
400
+ }
401
+ await client.delete(`/asset/${asset._id}`, { params: { type } });
402
+ return console.info("Asset ${assetMeta.name} has been deleted successfully");
403
+ }
404
+ function delete_default(program2) {
405
+ return program2.command("asset delete", "Delete objects of the API.").option(
406
+ "--path <path>",
407
+ "Path of the folder that container asset.yaml file and resources of it. Current working directory is the default value."
408
+ ).option("--type <type>", "Deletion type. Available options are 'soft' and 'hard'", {
409
+ required: true
410
+ }).action(_delete);
411
+ }
412
+
413
+ // apps/cli/src/commands/bucket/orm.ts
414
+ import fs6 from "fs";
415
+ import path5 from "path";
416
+ async function orm({ options }) {
417
+ const PATH = options.path || "";
418
+ const DESTINATION = path5.join(PATH, "bucket.ts");
419
+ const httpClient = await httpService.createFromCurrentCtx();
420
+ const { url } = await context.getCurrent();
421
+ await spin({
422
+ text: "Fetching buckets..",
423
+ op: async (spinner) => {
424
+ const buckets = await httpClient.get("/bucket");
425
+ const languages = await httpClient.get("/preference/bucket").then((r) => Object.keys(r.language.available));
426
+ spinner.text = "Building interface and method definitions..";
427
+ const warnings = [];
428
+ const content = Schema.createFileContent(buckets, languages, url, warnings);
429
+ spinner.text = "Writing to the destination..";
430
+ fs6.writeFileSync(DESTINATION, content);
431
+ spinner.text = `Succesfully completed! File url is ${PATH ? DESTINATION : path5.join(process.cwd(), "bucket.ts")}`;
432
+ if (warnings.length) {
433
+ spinner.warn(warnings.join());
434
+ }
435
+ }
436
+ });
437
+ }
438
+ function orm_default(program2) {
439
+ return program2.command(
440
+ "bucket orm",
441
+ "Create object relational mapping applied version of @spica-devkit/bucket"
442
+ ).option(
443
+ "--path <path>",
444
+ "Full URL of the destination folder that the file will be created into. The current directory will be used as default"
445
+ ).action(orm);
446
+ }
447
+ var Schema;
448
+ ((Schema2) => {
449
+ function createFileContent(buckets, languages, apiurl, warnings) {
450
+ const languagesDefinition = `${languages.map((i) => `'${i}'`).join("|")}`;
451
+ let lines = [];
452
+ lines.push("import * as Bucket from '@spica-devkit/bucket';");
453
+ lines.push(`
454
+ /**
455
+ * Call this method before interacting with buckets.
456
+ * @param initOptions Initialize options to initialize the '@spica-devkit/bucket'.
457
+ */
458
+ export function initialize(
459
+ ...initOptions: Parameters<typeof Bucket.initialize>
460
+ ) {
461
+ initOptions[0].publicUrl = '${apiurl}';
462
+ Bucket.initialize(...initOptions);
463
+ }`);
464
+ lines.push(
465
+ "\n\ntype Rest<T extends any[]> = ((...p: T) => void) extends ((p1: infer P1, ...rest: infer R) => void) ? R : never;"
466
+ );
467
+ lines.push("\ntype getArgs = Rest<Parameters<typeof Bucket.data.get>>;");
468
+ lines.push("\ntype getAllArgs = Rest<Parameters<typeof Bucket.data.getAll>>;");
469
+ lines.push("\ntype realtimeGetArgs = Rest<Parameters<typeof Bucket.data.realtime.get>>;");
470
+ lines.push("\ntype realtimeGetAllArgs = Rest<Parameters<typeof Bucket.data.realtime.getAll>>;");
471
+ lines.push("\ntype id = { _id: string };");
472
+ lines.push(`
473
+
474
+ type Find<Targets extends string[], Search extends string> = {
475
+ [K in keyof Targets]: Targets[K] extends \`\${Search}.\${infer Rest}\`
476
+ ? Targets[K]
477
+ : Targets[K] extends \`\${Search}\`
478
+ ? Targets[K]
479
+ : never;
480
+ }[number];
481
+
482
+ type FindRest<T extends string[], S extends string> = {
483
+ [K in keyof T]: T[K] extends \`\${S}.\${infer Rest}\`
484
+ ? Rest
485
+ : never
486
+ }[number];
487
+
488
+ type ConvertEnumToSelection<T extends string[]> = T extends []
489
+ ? []
490
+ : T extends (infer U)[]
491
+ ? [U, ...U[]]
492
+ : [];
493
+
494
+ type Props<T> = {
495
+ [K in keyof T]: K;
496
+ }[keyof T];
497
+
498
+ type RemoveProps<TClass, TProps extends keyof TClass> = {
499
+ [K in keyof TClass as K extends TProps ? never : K]: TClass[K];
500
+ };
501
+
502
+ type AvailableLanguages = ${languagesDefinition}
503
+
504
+ `);
505
+ buckets = makeTitlesUnique(buckets, warnings);
506
+ for (const bucket of buckets) {
507
+ buildInterface(bucket, languages, lines);
508
+ }
509
+ lines = replaceRelations(buckets, lines);
510
+ lines = addCrud(lines, buckets, languages);
511
+ return lines.join("");
512
+ }
513
+ Schema2.createFileContent = createFileContent;
514
+ function addCrud(lines, buckets, languages) {
515
+ const crud = `
516
+ class CRUD<Scheme,Paginate extends boolean = false> {
517
+ protected options: {
518
+ headers: {
519
+ 'accept-language'?: string;
520
+ };
521
+ queryParams: {
522
+ limit?: number;
523
+ skip?: number;
524
+ sort?: {
525
+ [T in keyof Scheme]: -1 | 1;
526
+ };
527
+ paginate?: boolean;
528
+
529
+ relation?: string[];
530
+ localize?: boolean;
531
+ filter?: any;
532
+ };
533
+ } = {
534
+ headers: {},
535
+ queryParams: {},
536
+ };
537
+
538
+ constructor(
539
+ protected bucketId: string,
540
+ protected bdService: typeof Bucket.data,
541
+ protected relationalFields: string[]
542
+ ) {}
543
+
544
+ private normalizeRelations(document: any) {
545
+ this.relationalFields.forEach((field) => {
546
+ if (typeof document[field] == 'object' && document[field] !== null) {
547
+ document[field] = Array.isArray(document[field])
548
+ ? document[field].map((v: any) => v._id || v)
549
+ : document[field]._id;
550
+ }
551
+ });
552
+ return document;
553
+ }
554
+
555
+ private buildOptions(options: any) {
556
+ options = options ? options : this.options;
557
+ this.options = {
558
+ headers: {},
559
+ queryParams: {},
560
+ };
561
+ return options;
562
+ }
563
+
564
+ get(...args: getArgs) {
565
+ args[1] = this.buildOptions(args[1]);
566
+ return this.bdService.get<Scheme & id>(this.bucketId, ...args);
567
+ }
568
+
569
+ getAll(
570
+ ...args: getAllArgs
571
+ ): Paginate extends true
572
+ ? Promise<Bucket.IndexResult<Scheme>>
573
+ : Promise<Scheme[]> {
574
+ args[0] = this.buildOptions(args[0]);
575
+ return this.bdService.getAll<Scheme & id>(
576
+ this.bucketId,
577
+ ...args
578
+ ) as Paginate extends true
579
+ ? Promise<Bucket.IndexResult<Scheme>>
580
+ : Promise<Scheme[]>;
581
+ }
582
+
583
+ insert(document: Omit<Scheme, '_id'>) {
584
+ document = this.normalizeRelations(document);
585
+ return this.bdService.insert(this.bucketId, document);
586
+ }
587
+ update(document: Scheme & id) {
588
+ document = this.normalizeRelations(document);
589
+ return this.bdService.update(this.bucketId, document._id, document);
590
+ }
591
+ patch(document: Partial<Scheme> & id) {
592
+ document = this.normalizeRelations(document);
593
+ return this.bdService.patch(this.bucketId, document._id, document);
594
+ }
595
+
596
+ remove(documentId: string) {
597
+ return this.bdService.remove(this.bucketId, documentId);
598
+ }
599
+
600
+ realtime = {
601
+ get: (...args: realtimeGetArgs) => {
602
+ return this.bdService.realtime.get<Scheme & id>(
603
+ this.bucketId,
604
+ ...args
605
+ );
606
+ },
607
+ getAll: (...args: realtimeGetAllArgs) => {
608
+ return this.bdService.realtime.getAll<Scheme & id>(
609
+ this.bucketId,
610
+ ...args
611
+ );
612
+ },
613
+ };
614
+ }
615
+
616
+ class Cursor<Scheme,Paginate extends boolean = false> extends CRUD<Scheme,Paginate>{
617
+ limit(limit: number): any {
618
+ this.options.queryParams.limit = limit;
619
+ return this;
620
+ }
621
+
622
+ skip(skip: number): any {
623
+ this.options.queryParams.skip = skip;
624
+ return this;
625
+ }
626
+
627
+ sort(sort: { [T in keyof Scheme]: -1 | 1 }): any {
628
+ this.options.queryParams.sort = sort;
629
+ return this;
630
+ }
631
+
632
+ filter(filter: any): any {
633
+ this.options.queryParams.filter = filter;
634
+ return this;
635
+ }
636
+
637
+ translate(language: AvailableLanguages): any {
638
+ this.options.headers['accept-language'] = language;
639
+ return this;
640
+ }
641
+
642
+ paginate(): any {
643
+ this.options.queryParams.paginate = true;
644
+ return this;
645
+ }
646
+
647
+ nonLocalize(): any {
648
+ this.options.queryParams.localize = false;
649
+ return this;
650
+ }
651
+
652
+ resolveRelations(relation:any): any {
653
+ this.options.queryParams.relation = relation;
654
+ return this;
655
+ }
656
+ }
657
+ `;
658
+ lines.push("\n");
659
+ lines.push(crud);
660
+ for (let bucket of buckets) {
661
+ const property = prepareNamespace(bucket.title);
662
+ const interfaceName = prepareInterfaceTitle(bucket.title);
663
+ const bucketId = bucket._id;
664
+ const bdService = "Bucket.data";
665
+ const relationalFields = getRelationFields(bucket.properties);
666
+ const relationalFieldsDefinition = `[${relationalFields.map((i) => `'${i}'`).join(",")}]`;
667
+ const resolveRelationEnums = getResolveRelationEnums(bucket._id, buckets);
668
+ const resolveRelationEnumsDefinition = resolveRelationEnums.length ? `(${resolveRelationEnums.map((i) => `'${i}'`).join("|")})[]` : "[]";
669
+ const crudDefinition = `
670
+ const ${interfaceName}RelationFields: string[] = ${relationalFieldsDefinition}
671
+ type ${interfaceName}RelationEnum = ${resolveRelationEnumsDefinition}
672
+ type ${interfaceName}RelationSelection = ConvertEnumToSelection<${interfaceName}RelationEnum>
673
+ type ${interfaceName}CursorMethods<
674
+ R extends string[] = [],
675
+ P extends boolean = false,
676
+ L extends boolean = true
677
+ > = RemoveProps<
678
+ ${interfaceName}Cursor<R, P, L>,
679
+ Exclude<Props<CRUD<${interfaceName}<R, L>, P>>, 'get' | 'getAll'>
680
+ >;
681
+
682
+ class ${interfaceName}Cursor<R extends string[] = [], P extends boolean = false, L extends boolean = true> extends Cursor<${interfaceName}<R,L>,P>{
683
+
684
+ override resolveRelations<Selecteds extends ${interfaceName}RelationSelection>(
685
+ relations: Selecteds
686
+ ): ${interfaceName}CursorMethods<Selecteds, P, L> {
687
+ return super.resolveRelations(relations);
688
+ }
689
+
690
+ override nonLocalize(): ${interfaceName}CursorMethods<R, P, false> {
691
+ return super.nonLocalize();
692
+ }
693
+
694
+ override limit(limit: number): ${interfaceName}CursorMethods<R, P, L> {
695
+ return super.limit(limit);
696
+ }
697
+
698
+ override skip(skip: number): ${interfaceName}CursorMethods<R, P, L> {
699
+ return super.skip(skip);
700
+ }
701
+
702
+ override sort(sort: {
703
+ [T in keyof ${interfaceName}]: -1 | 1;
704
+ }): ${interfaceName}CursorMethods<R, P, L> {
705
+ return super.sort(sort);
706
+ }
707
+
708
+ override filter(filter: any): ${interfaceName}CursorMethods<R, P, L> {
709
+ return super.filter(filter);
710
+ }
711
+
712
+ override translate(
713
+ language: AvailableLanguages
714
+ ): ${interfaceName}CursorMethods<R, P, L> {
715
+ return super.translate(language);
716
+ }
717
+
718
+ override paginate(): ${interfaceName}CursorMethods<R, true, L> {
719
+ return super.paginate();
720
+ }
721
+
722
+ }
723
+ `;
724
+ lines.push(crudDefinition);
725
+ const definition = `
726
+ export const ${property} = new ${interfaceName}Cursor('${bucketId}',${bdService},${interfaceName}RelationFields)
727
+ `;
728
+ lines.push(definition);
729
+ }
730
+ return lines;
731
+ }
732
+ function buildInterface(schema, languages, lines) {
733
+ const name = prepareInterfaceTitle(schema.title);
734
+ lines.push(
735
+ `
736
+
737
+ export interface ${name}<Relations extends string[] = [], Localize extends boolean = true>{`
738
+ );
739
+ lines.push(`
740
+ _id?: string;`);
741
+ buildProperties(schema.properties, languages, schema.required || [], "bucket", lines);
742
+ lines.push("\n}");
743
+ }
744
+ function prepareInterfaceTitle(str) {
745
+ str = replaceNonWords(str);
746
+ return str.charAt(0).toUpperCase() + str.slice(1);
747
+ }
748
+ function replaceRelations(buckets, lines) {
749
+ for (const bucket of buckets) {
750
+ const target = `<${bucket._id}>`;
751
+ lines = lines.map((line) => {
752
+ if (line.includes(target)) {
753
+ return line.replace(target, prepareInterfaceTitle(bucket.title));
754
+ }
755
+ return line;
756
+ });
757
+ }
758
+ return lines;
759
+ }
760
+ })(Schema || (Schema = {}));
761
+ function buildProperties(props, languages, reqs, bucketOrObject, lines) {
762
+ if (bucketOrObject == "object") {
763
+ lines.push("{");
764
+ }
765
+ for (const [key, value] of Object.entries(props)) {
766
+ const reqFlag = !reqs.includes(key) ? "?" : "";
767
+ lines.push(`
768
+ ${key + reqFlag}: `);
769
+ buildPropDef(key, value, languages, lines);
770
+ lines.push(";");
771
+ }
772
+ if (bucketOrObject == "object") {
773
+ lines.push("}");
774
+ }
775
+ }
776
+ function buildArray(key, def, languages, lines) {
777
+ buildPropDef(key, def, languages, lines);
778
+ lines.push("[]");
779
+ }
780
+ function buildPropDef(key, prop, languages, lines) {
781
+ if (prop.enum) {
782
+ lines.push("(");
783
+ lines.push(
784
+ // escape the quote character otherwise it breaks the definition
785
+ prop.enum.map((v) => prop.type == "string" ? `'${v.replace(/'/g, "\\'")}'` : v).join("|")
786
+ );
787
+ lines.push(")");
788
+ return;
789
+ }
790
+ switch (prop.type) {
791
+ case "string":
792
+ case "textarea":
793
+ case "color":
794
+ case "richtext":
795
+ case "storage":
796
+ let def = "string";
797
+ if (prop.options && prop.options.translate) {
798
+ let nonLocalizedType = {};
799
+ languages.forEach((l) => nonLocalizedType[l] = "string");
800
+ nonLocalizedType = JSON.stringify(nonLocalizedType);
801
+ def = `Localize extends true ? string : ${nonLocalizedType}`;
802
+ }
803
+ lines.push(def);
804
+ break;
805
+ case "number":
806
+ lines.push("number");
807
+ break;
808
+ case "date":
809
+ lines.push("Date | string");
810
+ break;
811
+ case "boolean":
812
+ lines.push("boolean");
813
+ break;
814
+ case "object":
815
+ buildProperties(prop.properties, prop.required || [], languages, "object", lines);
816
+ break;
817
+ case "array":
818
+ case "multiselect":
819
+ buildArray(key, prop.items, languages, lines);
820
+ break;
821
+ case "relation":
822
+ const suffix = prop.relationType == "onetomany" ? "[]" : "";
823
+ const definition = `Find<Relations,"${key}">[] extends never[] ? string${suffix} : (<${prop.bucketId}><FindRest<Relations,"${key}">[]>&id)${suffix}`;
824
+ lines.push(definition);
825
+ break;
826
+ case "location":
827
+ lines.push(`{ type: "Point", coordinates: [number,number]}`);
828
+ break;
829
+ default:
830
+ lines.push("any");
831
+ break;
832
+ }
833
+ }
834
+ function prepareNamespace(str) {
835
+ str = replaceNonWords(str);
836
+ str = str.toLowerCase();
837
+ if (str.match(/^[0-9]/)) {
838
+ str = "_" + str;
839
+ }
840
+ return str;
841
+ }
842
+ function replaceNonWords(str) {
843
+ return str.replace(/[^a-zA-Z0-9]/g, "_");
844
+ }
845
+ function getRelationFields(properties) {
846
+ const fields = [];
847
+ for (const [key, value] of Object.entries(properties)) {
848
+ if (value.type == "relation") {
849
+ fields.push(key);
850
+ }
851
+ }
852
+ return fields;
853
+ }
854
+ function makeTitlesUnique(buckets, warnings) {
855
+ const titles = [];
856
+ for (const bucket of buckets) {
857
+ const title = bucket.title;
858
+ const count = titles.filter((t) => t == title).length;
859
+ if (count >= 1) {
860
+ warnings.push(`It seems there is more than one bucket that has the title '${bucket.title}'.
861
+ Number suffix will be added but should use unique titles for the best practice.`);
862
+ bucket.title += count + 1;
863
+ }
864
+ titles.push(title);
865
+ }
866
+ return buckets;
867
+ }
868
+ function getResolveRelationEnums(id, buckets) {
869
+ const enums = [];
870
+ const maxDepth = 5;
871
+ const _getResolveRelationEnums = (_bucket, prefix) => {
872
+ const hasRelation = Object.values(_bucket.properties).some((v) => v.type == "relation");
873
+ if (!hasRelation) {
874
+ if (prefix && !enums.includes(prefix)) {
875
+ enums.push(prefix);
876
+ }
877
+ return;
878
+ }
879
+ Object.entries(_bucket.properties).forEach(([key, value]) => {
880
+ if (value.type == "relation") {
881
+ const paths = prefix ? `${prefix}.${key}` : key;
882
+ const isMaxLengthExceeded = paths.split(".").length > maxDepth;
883
+ if (isMaxLengthExceeded) {
884
+ return;
885
+ }
886
+ if (!enums.includes(paths)) {
887
+ enums.push(paths);
888
+ }
889
+ const relatedBuckets = buckets.filter((b) => b._id == value.bucketId);
890
+ relatedBuckets.forEach((b) => _getResolveRelationEnums(b, paths));
891
+ }
892
+ });
893
+ };
894
+ const targetBucket = buckets.find((b) => b._id == id);
895
+ _getResolveRelationEnums(targetBucket);
896
+ return enums;
897
+ }
898
+
899
+ // apps/cli/src/commands/context/ls.ts
900
+ async function listContexts() {
901
+ const currentContext = await config.get().catch((e) => {
902
+ console.warn(e.message);
903
+ return { context: void 0 };
904
+ });
905
+ const contexts = context.list().map((ctx) => {
906
+ let authorization = new Array(ctx.authorization.length).fill("*").slice(0, 10).join(" ");
907
+ if (ctx.name == currentContext.context) {
908
+ authorization = authorization + " (selected)";
909
+ }
910
+ return {
911
+ ...ctx,
912
+ authorization
913
+ };
914
+ });
915
+ console.table(contexts, ["name", "url", "authorization"]);
916
+ }
917
+ function ls_default(program2) {
918
+ return program2.command("context ls", "List contexts").action(listContexts);
919
+ }
920
+
921
+ // apps/cli/src/commands/context/remove.ts
922
+ async function removeContext({ args }) {
923
+ const name = args.name;
924
+ if (!context.has(name)) {
925
+ return console.error(`Context "${name}" does not exist.`);
926
+ }
927
+ context.remove(name);
928
+ console.info(`Context "${name}" has been deleted.`);
929
+ }
930
+ function remove_default(program2) {
931
+ return program2.command("context remove", "Remove context").argument("<name>", "Name of the context.").action(removeContext);
932
+ }
933
+
934
+ // apps/cli/src/commands/context/set.ts
935
+ import axios2 from "axios";
936
+ async function addContext({ options }) {
937
+ const name = options.name;
938
+ const apikey = options.apikey;
939
+ const url = options.url;
940
+ const rest = axios2.create({
941
+ baseURL: url,
942
+ headers: {
943
+ Authorization: `APIKEY ${apikey}`
944
+ }
945
+ });
946
+ try {
947
+ await rest.get("/passport/identity/statements");
948
+ const exists = context.has(name);
949
+ context.set(name, { url, authorization: `APIKEY ${apikey}` });
950
+ console.info(`Context "${name}" is successfully ${exists ? "set" : "added"}.`);
951
+ } catch (error) {
952
+ if (error.response) {
953
+ console.error(
954
+ `Error from server (${error.response.statusText}): ${error.response.data.message}`
955
+ );
956
+ } else if (error.request) {
957
+ console.error(
958
+ `Error (${error.code}): Something has happened while connecting, ${error.message}`
959
+ );
960
+ } else {
961
+ console.error(`Error: ${error.message}`);
962
+ }
963
+ }
964
+ }
965
+ function set_default(program2) {
966
+ return program2.command("context set", "Set context").option("--name <name>", "Name of the context.").option("--url <url>", "Url of the API.", { required: true }).option("-a <apikey>, --apikey <apikey>", "Authorize via an API Key.", { required: true }).action(addContext);
967
+ }
968
+
969
+ // apps/cli/src/commands/context/switch.ts
970
+ async function switchContext({ args }) {
971
+ const name = args.name;
972
+ if (!context.has(name)) {
973
+ console.error(`Could not found the context "${name}". You can add it by running; `);
974
+ console.log("");
975
+ console.log(`$ spica context add --name=${name} --url=URL_HERE --apikey=APIKEY_HERE`);
976
+ return;
977
+ }
978
+ await config.set({
979
+ context: name
980
+ });
981
+ console.info(`Context has been changed to "${name}".`);
982
+ }
983
+ function switch_default(program2) {
984
+ return program2.command("context switch", "Switch context").argument("<name>", "Name of the context.").action(switchContext);
985
+ }
986
+
987
+ // apps/cli/src/commands/function/orm.ts
988
+ import path7 from "path";
989
+ import fs7 from "fs";
990
+
991
+ // apps/cli/src/compile.ts
992
+ import ts5 from "typescript";
993
+
994
+ // apps/cli/src/function/helpers.ts
995
+ import ts from "typescript";
996
+ function getFunctionName(node) {
997
+ if (node.name) {
998
+ return node.name.escapedText;
999
+ }
1000
+ const isDefault = node.modifiers?.findIndex((m) => m.kind == ts.SyntaxKind.DefaultKeyword) != -1;
1001
+ if (isDefault) {
1002
+ return "default";
1003
+ }
1004
+ return void 0;
1005
+ }
1006
+
1007
+ // apps/cli/src/function/triggers/http/services/axios.ts
1008
+ import ts3 from "typescript";
1009
+
1010
+ // apps/cli/src/function/modifier.ts
1011
+ import ts2 from "typescript";
1012
+ var FunctionDeclarationModifier = class {
1013
+ constructor(node) {
1014
+ this.node = node;
1015
+ }
1016
+ setAllDeclarationDependencies() {
1017
+ this.modifiers = this.setModifiers();
1018
+ this.name = this.setName();
1019
+ this.body = this.setBody();
1020
+ this.parameters = this.setParameters();
1021
+ this.decorators = this.setDecorators();
1022
+ this.asteriksToken = this.setAsteriksToken();
1023
+ this.typeParameters = this.setTypeParameters();
1024
+ this.type = this.setType();
1025
+ }
1026
+ modify() {
1027
+ this.setAllDeclarationDependencies();
1028
+ return ts2.factory.updateFunctionDeclaration(
1029
+ this.node,
1030
+ this.modifiers,
1031
+ this.asteriksToken,
1032
+ this.name,
1033
+ this.typeParameters,
1034
+ this.parameters,
1035
+ this.type,
1036
+ this.body
1037
+ );
1038
+ }
1039
+ };
1040
+ var SpicaFunctionModifier = class extends FunctionDeclarationModifier {
1041
+ setBody() {
1042
+ return void 0;
1043
+ }
1044
+ setParameters() {
1045
+ return [];
1046
+ }
1047
+ setDecorators() {
1048
+ return void 0;
1049
+ }
1050
+ setAsteriksToken() {
1051
+ return void 0;
1052
+ }
1053
+ setTypeParameters() {
1054
+ return void 0;
1055
+ }
1056
+ setType() {
1057
+ return void 0;
1058
+ }
1059
+ constructor(node, handler) {
1060
+ super(node);
1061
+ this.handler = handler;
1062
+ }
1063
+ get isHandlerDefault() {
1064
+ return this.handler == "default";
1065
+ }
1066
+ setModifiers() {
1067
+ const modifiers = [ts2.factory.createModifier(ts2.SyntaxKind.ExportKeyword)];
1068
+ if (this.isHandlerDefault) {
1069
+ modifiers.push(ts2.factory.createModifier(ts2.SyntaxKind.DefaultKeyword));
1070
+ }
1071
+ return modifiers;
1072
+ }
1073
+ setName() {
1074
+ return !this.isHandlerDefault ? ts2.factory.createIdentifier(this.handler) : void 0;
1075
+ }
1076
+ getImports() {
1077
+ return [];
1078
+ }
1079
+ getExtraFunctionDeclarations() {
1080
+ return [];
1081
+ }
1082
+ };
1083
+
1084
+ // apps/cli/src/function/triggers/http/services/axios.ts
1085
+ var Axios = class extends SpicaFunctionModifier {
1086
+ constructor(node, handler, baseUrl, trigger, validators = axiosValidators) {
1087
+ super(node, handler);
1088
+ this.validators = validators;
1089
+ this.extraFunctionDeclarations = [];
1090
+ this.registeredValidators = [];
1091
+ this.url = `${baseUrl}/fn-execute${trigger.options.path}`;
1092
+ this.method = trigger.options.method;
1093
+ for (const factory of this.validators) {
1094
+ const emptyFn = ts3.factory.createFunctionDeclaration(
1095
+ [],
1096
+ void 0,
1097
+ void 0,
1098
+ [],
1099
+ [],
1100
+ void 0,
1101
+ void 0
1102
+ );
1103
+ const validator = factory(emptyFn);
1104
+ this.registeredValidators.push(validator);
1105
+ this.extraFunctionDeclarations.push(validator.modify());
1106
+ }
1107
+ }
1108
+ static {
1109
+ this.modifierName = "axios";
1110
+ }
1111
+ getExtraFunctionDeclarations() {
1112
+ return this.extraFunctionDeclarations;
1113
+ }
1114
+ getImports() {
1115
+ const importClause = ts3.factory.createImportClause(
1116
+ false,
1117
+ ts3.factory.createIdentifier("axios"),
1118
+ void 0
1119
+ );
1120
+ const importDeclaration = ts3.factory.createImportDeclaration(
1121
+ void 0,
1122
+ importClause,
1123
+ ts3.factory.createStringLiteral("axios")
1124
+ );
1125
+ return [importDeclaration];
1126
+ }
1127
+ setParameters() {
1128
+ return [
1129
+ ts3.factory.createParameterDeclaration(
1130
+ void 0,
1131
+ void 0,
1132
+ ts3.factory.createIdentifier("config"),
1133
+ void 0,
1134
+ void 0,
1135
+ void 0
1136
+ )
1137
+ ];
1138
+ }
1139
+ setBody() {
1140
+ const configValue = ts3.factory.createObjectLiteralExpression([
1141
+ ts3.factory.createSpreadAssignment(ts3.factory.createIdentifier("config")),
1142
+ ts3.factory.createPropertyAssignment(
1143
+ ts3.factory.createIdentifier("method"),
1144
+ ts3.factory.createStringLiteral(this.method.toLowerCase())
1145
+ ),
1146
+ ts3.factory.createPropertyAssignment(
1147
+ ts3.factory.createIdentifier("url"),
1148
+ ts3.factory.createStringLiteral(this.url)
1149
+ )
1150
+ ]);
1151
+ const configAssigment = ts3.factory.createBinaryExpression(
1152
+ ts3.factory.createIdentifier("config"),
1153
+ ts3.SyntaxKind.FirstAssignment,
1154
+ configValue
1155
+ );
1156
+ const configStatement = ts3.factory.createExpressionStatement(configAssigment);
1157
+ const validatorCalls = [];
1158
+ for (const validator of this.registeredValidators) {
1159
+ const call = ts3.factory.createCallExpression(validator.name, void 0, [
1160
+ ts3.factory.createIdentifier("config")
1161
+ ]);
1162
+ validatorCalls.push(ts3.factory.createExpressionStatement(call));
1163
+ }
1164
+ const requestAccess = ts3.factory.createPropertyAccessExpression(
1165
+ ts3.factory.createIdentifier("axios"),
1166
+ ts3.factory.createIdentifier("request")
1167
+ );
1168
+ const requestArgs = [ts3.factory.createIdentifier("config")];
1169
+ const requestCall = ts3.factory.createCallExpression(requestAccess, void 0, requestArgs);
1170
+ const thenParams = [
1171
+ ts3.factory.createParameterDeclaration(
1172
+ void 0,
1173
+ void 0,
1174
+ ts3.factory.createIdentifier("r"),
1175
+ void 0,
1176
+ void 0,
1177
+ void 0
1178
+ )
1179
+ ];
1180
+ const thenBody = ts3.factory.createPropertyAccessExpression(
1181
+ ts3.factory.createIdentifier("r"),
1182
+ ts3.factory.createIdentifier("data")
1183
+ );
1184
+ const thenArgs = ts3.factory.createArrowFunction(
1185
+ void 0,
1186
+ void 0,
1187
+ thenParams,
1188
+ void 0,
1189
+ ts3.factory.createToken(ts3.SyntaxKind.EqualsGreaterThanToken),
1190
+ thenBody
1191
+ );
1192
+ const thenAccess = ts3.factory.createPropertyAccessExpression(
1193
+ requestCall,
1194
+ ts3.factory.createIdentifier("then")
1195
+ );
1196
+ const thenCall = ts3.factory.createCallExpression(thenAccess, void 0, [thenArgs]);
1197
+ const returnStatement = ts3.factory.createReturnStatement(thenCall);
1198
+ return ts3.factory.createBlock([configStatement, ...validatorCalls, returnStatement], true);
1199
+ }
1200
+ };
1201
+ var AxiosWriteValidator = class _AxiosWriteValidator extends FunctionDeclarationModifier {
1202
+ static {
1203
+ this.modifierName = "axiosWriteValidator";
1204
+ }
1205
+ setAsteriksToken() {
1206
+ return void 0;
1207
+ }
1208
+ setTypeParameters() {
1209
+ return [];
1210
+ }
1211
+ setModifiers() {
1212
+ return [];
1213
+ }
1214
+ setType() {
1215
+ return void 0;
1216
+ }
1217
+ setDecorators() {
1218
+ return [];
1219
+ }
1220
+ getImports() {
1221
+ return [];
1222
+ }
1223
+ getExtraFunctionDeclarations() {
1224
+ return [];
1225
+ }
1226
+ setName() {
1227
+ return ts3.factory.createIdentifier(_AxiosWriteValidator.modifierName);
1228
+ }
1229
+ setParameters() {
1230
+ return [
1231
+ ts3.factory.createParameterDeclaration(
1232
+ void 0,
1233
+ void 0,
1234
+ ts3.factory.createIdentifier("config")
1235
+ )
1236
+ ];
1237
+ }
1238
+ setBody() {
1239
+ const writeMethods = ["post", "put", "patch"];
1240
+ const writeMethodsArray = ts3.factory.createArrayLiteralExpression(
1241
+ writeMethods.map((m) => ts3.factory.createStringLiteral(m)),
1242
+ false
1243
+ );
1244
+ const includes = ts3.factory.createPropertyAccessExpression(
1245
+ writeMethodsArray,
1246
+ ts3.factory.createIdentifier("includes")
1247
+ );
1248
+ const configMethodAccess = ts3.factory.createPropertyAccessExpression(
1249
+ ts3.factory.createIdentifier("config"),
1250
+ ts3.factory.createIdentifier("method")
1251
+ );
1252
+ const includesCall = ts3.factory.createCallExpression(includes, void 0, [configMethodAccess]);
1253
+ const configData = ts3.factory.createPropertyAccessExpression(
1254
+ ts3.factory.createIdentifier("config"),
1255
+ ts3.factory.createIdentifier("data")
1256
+ );
1257
+ const binaryExpression = ts3.factory.createLogicalAnd(
1258
+ includesCall,
1259
+ ts3.factory.createPrefixUnaryExpression(ts3.SyntaxKind.ExclamationToken, configData)
1260
+ );
1261
+ const warningMessage = `Sending empty request body for ${writeMethods.join(
1262
+ ", "
1263
+ )} requests is unusual. If it's not intented, please use config.data or update your spica function.`;
1264
+ const consoleWarnAccess = ts3.factory.createPropertyAccessExpression(
1265
+ ts3.factory.createIdentifier("console"),
1266
+ ts3.factory.createIdentifier("warn")
1267
+ );
1268
+ const consoleWarnCall = ts3.factory.createCallExpression(
1269
+ consoleWarnAccess,
1270
+ [],
1271
+ [ts3.factory.createStringLiteral(warningMessage)]
1272
+ );
1273
+ const ifStatementBody = ts3.factory.createBlock(
1274
+ [ts3.factory.createExpressionStatement(consoleWarnCall)],
1275
+ true
1276
+ );
1277
+ const IfStatement = ts3.factory.createIfStatement(binaryExpression, ifStatementBody);
1278
+ return ts3.factory.createBlock([IfStatement], true);
1279
+ }
1280
+ };
1281
+ var AxiosReadValidator = class _AxiosReadValidator extends FunctionDeclarationModifier {
1282
+ static {
1283
+ this.modifierName = "axiosReadValidator";
1284
+ }
1285
+ setAsteriksToken() {
1286
+ return void 0;
1287
+ }
1288
+ setTypeParameters() {
1289
+ return [];
1290
+ }
1291
+ setModifiers() {
1292
+ return [];
1293
+ }
1294
+ setType() {
1295
+ return void 0;
1296
+ }
1297
+ setDecorators() {
1298
+ return [];
1299
+ }
1300
+ getImports() {
1301
+ return [];
1302
+ }
1303
+ getExtraFunctionDeclarations() {
1304
+ return [];
1305
+ }
1306
+ setName() {
1307
+ return ts3.factory.createIdentifier(_AxiosReadValidator.modifierName);
1308
+ }
1309
+ setParameters() {
1310
+ return [
1311
+ ts3.factory.createParameterDeclaration(
1312
+ void 0,
1313
+ void 0,
1314
+ ts3.factory.createIdentifier("config")
1315
+ )
1316
+ ];
1317
+ }
1318
+ setBody() {
1319
+ const readMethods = ["get", "delete", "trace", "options", "head"];
1320
+ const readMethodsArray = ts3.factory.createArrayLiteralExpression(
1321
+ readMethods.map((m) => ts3.factory.createStringLiteral(m)),
1322
+ false
1323
+ );
1324
+ const includes = ts3.factory.createPropertyAccessExpression(
1325
+ readMethodsArray,
1326
+ ts3.factory.createIdentifier("includes")
1327
+ );
1328
+ const configMethodAccess = ts3.factory.createPropertyAccessExpression(
1329
+ ts3.factory.createIdentifier("config"),
1330
+ ts3.factory.createIdentifier("method")
1331
+ );
1332
+ const includesCall = ts3.factory.createCallExpression(includes, void 0, [configMethodAccess]);
1333
+ const configData = ts3.factory.createPropertyAccessExpression(
1334
+ ts3.factory.createIdentifier("config"),
1335
+ ts3.factory.createIdentifier("data")
1336
+ );
1337
+ const binaryExpression = ts3.factory.createLogicalAnd(includesCall, configData);
1338
+ const warningMessage = `Sending request body for ${readMethods.join(
1339
+ ", "
1340
+ )} requests is unusual. If it's not intented, please remove config.data or update your spica function.`;
1341
+ const consoleWarnAccess = ts3.factory.createPropertyAccessExpression(
1342
+ ts3.factory.createIdentifier("console"),
1343
+ ts3.factory.createIdentifier("warn")
1344
+ );
1345
+ const consoleWarnCall = ts3.factory.createCallExpression(
1346
+ consoleWarnAccess,
1347
+ [],
1348
+ [ts3.factory.createStringLiteral(warningMessage)]
1349
+ );
1350
+ const ifStatementBody = ts3.factory.createBlock(
1351
+ [ts3.factory.createExpressionStatement(consoleWarnCall)],
1352
+ true
1353
+ );
1354
+ const IfStatement = ts3.factory.createIfStatement(binaryExpression, ifStatementBody);
1355
+ return ts3.factory.createBlock([IfStatement], true);
1356
+ }
1357
+ };
1358
+ var axiosValidators = [
1359
+ (node) => new AxiosWriteValidator(node),
1360
+ (node) => new AxiosReadValidator(node)
1361
+ ];
1362
+ var axios3 = {
1363
+ name: Axios.modifierName,
1364
+ factory: (node, handler, baseUrl, trigger) => new Axios(node, handler, baseUrl, trigger)
1365
+ };
1366
+
1367
+ // apps/cli/src/function/triggers/http/services/index.ts
1368
+ var httpServiceModifiers = /* @__PURE__ */ new Map([[axios3.name, axios3.factory]]);
1369
+
1370
+ // apps/cli/src/function/triggers/http/transformer.ts
1371
+ import ts4 from "typescript";
1372
+ var HttpTransformer = class {
1373
+ constructor(triggers, baseUrl, options, modifiers = httpServiceModifiers) {
1374
+ this.triggers = triggers;
1375
+ this.baseUrl = baseUrl;
1376
+ this.options = options;
1377
+ this.modifiers = modifiers;
1378
+ this.importsAdded = false;
1379
+ this.extraFnsAdded = false;
1380
+ const factory = this.modifiers.get(options.selectedService);
1381
+ if (!factory) {
1382
+ throw new Error(`Http service named ${options.selectedService} is not implemented yet`);
1383
+ }
1384
+ this.modifier = factory;
1385
+ }
1386
+ static {
1387
+ this._name = "http";
1388
+ }
1389
+ getTransformer() {
1390
+ const transformer = (context2) => (rootNode) => {
1391
+ const visitor = this.getVisitor(this.triggers, context2);
1392
+ return ts4.visitNode(rootNode, visitor);
1393
+ };
1394
+ return transformer;
1395
+ }
1396
+ getVisitor(triggers, context2) {
1397
+ const visitor = (node) => {
1398
+ if (ts4.isSourceFile(node)) {
1399
+ return ts4.visitEachChild(node, visitor, context2);
1400
+ }
1401
+ const handler = ts4.isFunctionDeclaration(node) ? getFunctionName(node) : void 0;
1402
+ if (!handler || !triggers[handler]) {
1403
+ return node;
1404
+ }
1405
+ const modifier = this.modifier(
1406
+ node,
1407
+ handler,
1408
+ this.baseUrl,
1409
+ triggers[handler]
1410
+ );
1411
+ if (!this.importsAdded) {
1412
+ this.options.addImports(modifier.getImports());
1413
+ this.importsAdded = true;
1414
+ }
1415
+ if (!this.extraFnsAdded) {
1416
+ this.options.addExtraFunctions(modifier.getExtraFunctionDeclarations());
1417
+ this.extraFnsAdded = true;
1418
+ }
1419
+ return modifier.modify();
1420
+ };
1421
+ return visitor;
1422
+ }
1423
+ };
1424
+
1425
+ // apps/cli/src/function/triggers/http/index.ts
1426
+ var http = {
1427
+ name: HttpTransformer._name,
1428
+ factory: (triggers, baseUrl, options) => new HttpTransformer(triggers, baseUrl, options, httpServiceModifiers)
1429
+ };
1430
+
1431
+ // apps/cli/src/function/triggers/index.ts
1432
+ var triggerTransformers = /* @__PURE__ */ new Map([
1433
+ [http.name, http.factory]
1434
+ ]);
1435
+
1436
+ // apps/cli/src/compile.ts
1437
+ var FunctionCompiler = class {
1438
+ constructor(fn, triggerTypes, baseUrl, triggerOptions, transformers = triggerTransformers) {
1439
+ this.fn = fn;
1440
+ this.triggerTypes = triggerTypes;
1441
+ this.baseUrl = baseUrl;
1442
+ this.triggerOptions = triggerOptions;
1443
+ this.transformers = transformers;
1444
+ this.fn.triggers = this.filterTriggers(
1445
+ this.fn.triggers,
1446
+ ([_, trigger]) => this.triggerTypes.includes(trigger.type)
1447
+ );
1448
+ this.sourceFile = ts5.createSourceFile(fn.name, fn.index, ts5.ScriptTarget.Latest);
1449
+ }
1450
+ compile() {
1451
+ const transformers = [];
1452
+ const imports = [];
1453
+ const extraFunctions = [];
1454
+ const addImports = (_imports) => imports.push(..._imports);
1455
+ const addExtraFunctions = (fns) => extraFunctions.push(...fns);
1456
+ const handlerNames = this.getHandlerNames();
1457
+ const handlerFilterer = this.getHandlerFiltererTransformer(handlerNames);
1458
+ transformers.push(handlerFilterer);
1459
+ for (const triggerType of this.triggerTypes) {
1460
+ const factory = this.transformers.get(triggerType);
1461
+ if (!factory) {
1462
+ throw Error(`Trigger type ${triggerType} does not have any transformer.`);
1463
+ }
1464
+ const relevantTriggers = this.filterTriggers(
1465
+ this.fn.triggers,
1466
+ ([_, trigger]) => trigger.type == triggerType
1467
+ );
1468
+ if (!Object.keys(relevantTriggers).length) {
1469
+ continue;
1470
+ }
1471
+ const transformerOptions = {
1472
+ ...this.triggerOptions[triggerType],
1473
+ addImports,
1474
+ addExtraFunctions
1475
+ };
1476
+ const transformer = factory(relevantTriggers, this.baseUrl, transformerOptions);
1477
+ transformers.push(transformer.getTransformer());
1478
+ }
1479
+ this.sourceFile = ts5.transform(this.sourceFile, transformers).transformed[0];
1480
+ this.sourceFile = ts5.factory.updateSourceFile(this.sourceFile, [
1481
+ ...imports,
1482
+ ...this.sourceFile.statements,
1483
+ ...extraFunctions
1484
+ ]);
1485
+ const code = ts5.createPrinter().printFile(this.sourceFile);
1486
+ switch (this.fn.language) {
1487
+ case "typescript":
1488
+ return [{ extension: "ts", content: code }];
1489
+ case "javascript":
1490
+ const types = this.generateTypesOfCode(code);
1491
+ return [
1492
+ { extension: "js", content: code },
1493
+ { extension: "d.ts", content: types }
1494
+ ];
1495
+ default:
1496
+ throw Error(`Language named ${this.fn.language} has no compiler yet.`);
1497
+ }
1498
+ }
1499
+ getHandlerNames() {
1500
+ return Object.keys(this.fn.triggers);
1501
+ }
1502
+ filterTriggers(triggers, filter) {
1503
+ return Object.entries(triggers).filter(filter).reduce((acc, [handler, trigger]) => {
1504
+ acc[handler] = trigger;
1505
+ return acc;
1506
+ }, {});
1507
+ }
1508
+ getHandlerFiltererTransformer(handlerNames) {
1509
+ const transformer = (context2) => (rootNode) => {
1510
+ const visitor = (node) => {
1511
+ const isRootNode = node.kind == ts5.SyntaxKind.SourceFile;
1512
+ if (isRootNode) {
1513
+ return ts5.visitEachChild(node, visitor, context2);
1514
+ }
1515
+ const isNecessaryStatement = ts5.isFunctionDeclaration(node) ? handlerNames.includes(getFunctionName(node)) : false;
1516
+ if (!isNecessaryStatement) {
1517
+ return this.disableStatement(node);
1518
+ }
1519
+ return node;
1520
+ };
1521
+ return ts5.visitNode(rootNode, visitor);
1522
+ };
1523
+ return transformer;
1524
+ }
1525
+ codeToAst(code) {
1526
+ return ts5.createSourceFile("name.ts", code, ts5.ScriptTarget.Latest);
1527
+ }
1528
+ // export function clearEmptyStatementTraces(code: string) {
1529
+ // return code.replace(/^;[\n|;]*$\n/gm, "");
1530
+ // }
1531
+ disableStatement(node) {
1532
+ node = ts5.factory.createEmptyStatement();
1533
+ node = ts5.addSyntheticLeadingComment(
1534
+ node,
1535
+ ts5.SyntaxKind.SingleLineCommentTrivia,
1536
+ " This statement has been deleted."
1537
+ );
1538
+ return node;
1539
+ }
1540
+ generateTypesOfCode(code) {
1541
+ let types;
1542
+ const options = {
1543
+ allowJs: true,
1544
+ declaration: true,
1545
+ emitDeclarationOnly: true
1546
+ };
1547
+ const host = ts5.createCompilerHost(options);
1548
+ host.writeFile = (_, data) => {
1549
+ types = data;
1550
+ };
1551
+ host.readFile = () => code;
1552
+ const program2 = ts5.createProgram(["dummy.js"], options, host);
1553
+ program2.emit();
1554
+ return types;
1555
+ }
1556
+ };
1557
+
1558
+ // apps/cli/src/validator.ts
1559
+ import { bold as bold2, green, red as red2 } from "colorette";
1560
+ import * as path6 from "path";
1561
+ function projectName(input) {
1562
+ if (input && !input.match(/^[a-z][a-z0-9]+(?:-[a-z0-9]+)*$/)) {
1563
+ throw new Error(
1564
+ `${red2(input)} is an invalid project name.
1565
+ It should be in format like ${bold2("infra1")}, ${bold2("infra-1")} or ${bold2("project-1")}`
1566
+ );
1567
+ }
1568
+ return input;
1569
+ }
1570
+ function projectLocalResourceFolder(input) {
1571
+ if (!path6.isAbsolute(input)) {
1572
+ throw new Error(
1573
+ `${red2(input)} is not an absolute path.
1574
+ Please provide a full path starting with ${bold2("/")}, like ${bold2("/home/user/project")}`
1575
+ );
1576
+ }
1577
+ return input;
1578
+ }
1579
+ var availableSyncModules = [
1580
+ "bucket",
1581
+ "function",
1582
+ "bucket-data",
1583
+ "apikey",
1584
+ "policy",
1585
+ "env-var"
1586
+ ];
1587
+ function validateSyncModules(input) {
1588
+ const moduleNames = input.split(",").map((m) => m.trim());
1589
+ if (!moduleNames.length) {
1590
+ throw new Error(`You should select one or more of these modules:
1591
+ ${availableSyncModules.map((m) => `- ${green(m)}`).join("\n")}`);
1592
+ }
1593
+ const nonMigratables = moduleNames.filter((module) => availableSyncModules.indexOf(module) == -1);
1594
+ if (nonMigratables.length) {
1595
+ throw new Error(`Some of selected modules are not valid:
1596
+ ${nonMigratables.map((m) => `- ${red2(m)}`).join("\n")}
1597
+
1598
+ Synchronizable modules are:
1599
+ ${availableSyncModules.map((m) => `- ${green(m)}`).join("\n")}
1600
+ `);
1601
+ }
1602
+ return input;
1603
+ }
1604
+ function validateSyncIds(input) {
1605
+ if (typeof input !== "string") {
1606
+ throw new Error("You should provide a comma separated string");
1607
+ }
1608
+ return input;
1609
+ }
1610
+ var availableHttpServices = ["axios"];
1611
+ function separateToNewLines(iterable, colorCtor) {
1612
+ return iterable.map((i) => `- ${colorCtor ? colorCtor(i) : i}`).join("\n");
1613
+ }
1614
+
1615
+ // apps/cli/src/commands/function/orm.ts
1616
+ import { green as green2 } from "colorette";
1617
+ async function orm2({ options }) {
1618
+ const PATH = options.path || path7.join(process.cwd(), "functions");
1619
+ const TRIGGER_TYPES = options.triggerTypes;
1620
+ const HTTP_SERVICE = options.httpService;
1621
+ const httpClient = await httpService.createFromCurrentCtx();
1622
+ const { url } = await context.getCurrent();
1623
+ await spin({
1624
+ text: "Fetching functions..",
1625
+ op: async (spinner) => {
1626
+ const functions = await httpClient.get("/function").then((r) => {
1627
+ const fns = r;
1628
+ const promises = fns.map(
1629
+ (fn) => httpClient.get(`/function/${fn._id}/index`).then((r2) => {
1630
+ fn.index = r2.index;
1631
+ })
1632
+ );
1633
+ return Promise.all(promises).then(() => fns);
1634
+ });
1635
+ spinner.text = "Building interface and method definitions..";
1636
+ if (!fs7.existsSync(PATH)) {
1637
+ fs7.mkdirSync(PATH);
1638
+ }
1639
+ const writeFilePromises = [];
1640
+ for (const fn of functions) {
1641
+ const options2 = {
1642
+ http: { selectedService: HTTP_SERVICE }
1643
+ };
1644
+ const sources = new FunctionCompiler(fn, TRIGGER_TYPES, url, options2).compile();
1645
+ const replacedName = fn.name.replace(/ /gm, "_").replace(/\//g, "__");
1646
+ const folderPath = path7.join(PATH, replacedName);
1647
+ if (!fs7.existsSync(folderPath)) {
1648
+ fs7.mkdirSync(folderPath);
1649
+ }
1650
+ for (const source of sources) {
1651
+ const _path2 = path7.join(folderPath, `index.${source.extension}`);
1652
+ writeFilePromises.push(fs7.promises.writeFile(_path2, source.content, {}));
1653
+ }
1654
+ }
1655
+ spinner.text = "Writing to the destination..";
1656
+ await Promise.all(writeFilePromises);
1657
+ spinner.text = `Succesfully completed! Folder url is ${PATH}`;
1658
+ }
1659
+ });
1660
+ }
1661
+ function orm_default2(program2) {
1662
+ return program2.command(
1663
+ "function orm",
1664
+ "Create object relational mapping applied files to interact with Spica Cloud Functions"
1665
+ ).option(
1666
+ "--path <path>",
1667
+ "Full URL of the destination folder that the files will be created into. The current directory will be used as default"
1668
+ ).option(
1669
+ "--trigger-types <trigger-types>",
1670
+ "Trigger types that will be filtered. Default value is http.",
1671
+ {
1672
+ default: ["http"]
1673
+ }
1674
+ ).option(
1675
+ "--http-service <http-service>",
1676
+ `Third party library that is responsible for sending http requests. Available services are: ${separateToNewLines(
1677
+ availableHttpServices,
1678
+ green2
1679
+ )}}.`,
1680
+ {
1681
+ default: "axios"
1682
+ }
1683
+ ).action(orm2);
1684
+ }
1685
+
1686
+ // apps/cli/src/commands/project/ls.ts
1687
+ import docker from "dockerode";
1688
+ import duration from "pretty-ms";
1689
+ async function list() {
1690
+ const machine2 = new docker();
1691
+ const foundNetworks = await machine2.listNetworks({
1692
+ filters: JSON.stringify({ label: { namespace: true } })
1693
+ });
1694
+ const instances = [];
1695
+ for (const network of foundNetworks) {
1696
+ const containers = await machine2.listContainers({
1697
+ all: true,
1698
+ filters: JSON.stringify({ label: [`namespace=${network["Labels"].namespace}`] })
1699
+ });
1700
+ const namespace = network["Labels"].namespace;
1701
+ const instance = {
1702
+ NAMESPACE: namespace,
1703
+ DESCRIPTION: [],
1704
+ PORT: "-",
1705
+ AGE: "-",
1706
+ VERSION: "-",
1707
+ STATUS: "-"
1708
+ };
1709
+ instances.push(instance);
1710
+ const api = containers.find(
1711
+ (container) => container.Names.some((name) => name.indexOf(`${namespace}-api`) != -1)
1712
+ );
1713
+ const spica = containers.find(
1714
+ (container) => container.Names.some((name) => name.indexOf(`${namespace}-spica`) != -1)
1715
+ );
1716
+ const ingress = containers.find(
1717
+ (container) => container.Names.some((name) => name.indexOf(`${namespace}-ingress`) != -1)
1718
+ );
1719
+ if (api && spica && ingress) {
1720
+ instance.VERSION = api.Image.split(":")[1];
1721
+ instance.AGE = duration(new Date(api.Created * 1e3).getTime());
1722
+ if (ingress.State == "running" && ingress.Ports.length) {
1723
+ const [port] = ingress.Ports;
1724
+ instance.PORT = `${port.IP}:${port.PublicPort}`;
1725
+ }
1726
+ if (api.State == "running" && spica.State == "running" && ingress.State == "running") {
1727
+ instance.STATE = ingress.State;
1728
+ instance.STATUS = ingress.Status;
1729
+ } else {
1730
+ const downContainer = ingress.State != "running" ? ingress : api.State != "running" ? api : spica;
1731
+ instance.STATUS = downContainer.Status;
1732
+ instance.DESCRIPTION.push(
1733
+ `The container ${downContainer.Image.startsWith("nginx") ? "Ingress" : downContainer.Names[0].slice(1)} is down.`
1734
+ );
1735
+ }
1736
+ }
1737
+ const databaseContainers = containers.filter((container) => container.Image.startsWith("mongo"));
1738
+ const runningDatabaseContainers = containers.filter(
1739
+ (container) => container.Image.startsWith("mongo") && container.State == "running"
1740
+ );
1741
+ if (runningDatabaseContainers.length != databaseContainers.length) {
1742
+ instance.DESCRIPTION.push(`Some database containers are down.`);
1743
+ }
1744
+ }
1745
+ console.table(instances, ["NAMESPACE", "AGE", "STATUS", "VERSION", "PORT", "DESCRIPTION"]);
1746
+ }
1747
+ function ls_default2(program2) {
1748
+ return program2.command("project ls", "List local projects.").action(list);
1749
+ }
1750
+
1751
+ // apps/cli/src/commands/project/remove.ts
1752
+ import docker2 from "dockerode";
1753
+ async function remove({ args, options }) {
1754
+ const { name } = args;
1755
+ const machine2 = new docker2();
1756
+ const foundNetworks = await machine2.listNetworks({
1757
+ filters: JSON.stringify({ label: [`namespace=${name}`] })
1758
+ });
1759
+ const foundContainers = await machine2.listContainers({
1760
+ all: true,
1761
+ filters: JSON.stringify({ label: [`namespace=${name}`] })
1762
+ });
1763
+ if (!foundNetworks.length && !foundContainers.length) {
1764
+ return console.warn(`There is no instance with name ${name}.`);
1765
+ }
1766
+ if (foundContainers.length) {
1767
+ await spin({
1768
+ text: `Removing containers (0/${foundContainers.length}).`,
1769
+ op: (spinner) => {
1770
+ let deleted = 0;
1771
+ function increaseDeletedCount() {
1772
+ deleted++;
1773
+ spinner.text = `Removing containers (${deleted}/${foundContainers.length}).`;
1774
+ }
1775
+ return Promise.all(
1776
+ foundContainers.map(
1777
+ async (containerInfo) => machine2.getContainer(containerInfo.Id).remove({
1778
+ v: true,
1779
+ // Remove volumes attached to the container
1780
+ force: true
1781
+ // Stop if the container running
1782
+ }).then(() => increaseDeletedCount())
1783
+ )
1784
+ );
1785
+ }
1786
+ });
1787
+ }
1788
+ if (foundNetworks.length) {
1789
+ await spin({
1790
+ text: `Removing networks (0/${foundNetworks.length}).`,
1791
+ op: (spinner) => {
1792
+ let deleted = 0;
1793
+ function increaseDeletedCount() {
1794
+ deleted++;
1795
+ spinner.text = `Removing networks (${deleted}/${foundNetworks.length}).`;
1796
+ }
1797
+ return Promise.all(
1798
+ foundNetworks.map(
1799
+ (network) => machine2.getNetwork(network.Id).remove().then(() => increaseDeletedCount())
1800
+ )
1801
+ );
1802
+ }
1803
+ });
1804
+ }
1805
+ if (!options.retainVolumes) {
1806
+ const foundVolumes = (await machine2.listVolumes({
1807
+ filters: JSON.stringify({ label: [`namespace=${name}`] })
1808
+ })).Volumes;
1809
+ await spin({
1810
+ text: `Removing volumes (0/${foundVolumes.length}).`,
1811
+ op: (spinner) => {
1812
+ let deleted = 0;
1813
+ function increaseDeletedCount() {
1814
+ deleted++;
1815
+ spinner.text = `Removing volumes (${deleted}/${foundVolumes.length}).`;
1816
+ }
1817
+ return Promise.all(
1818
+ foundVolumes.map(
1819
+ (volume) => machine2.getVolume(volume.Name).remove().then(() => increaseDeletedCount())
1820
+ )
1821
+ );
1822
+ }
1823
+ });
1824
+ }
1825
+ console.info(`Spica ${name} was successfully deleted.`);
1826
+ }
1827
+ function remove_default2(program2) {
1828
+ return program2.command("project remove", "Stop and remove a local project.").argument("<name>", "Name of the project to remove.", { validator: projectName }).option("--retain-volumes", "When false, the data will be removed along with the containers.", {
1829
+ default: true
1830
+ }).action(remove);
1831
+ }
1832
+
1833
+ // apps/cli/src/commands/project/start.ts
1
1834
  import caporalCore from "@caporal/core";
2
- const { program } = caporalCore;
3
- import "./src/console.js";
4
- import assetApply from "./src/commands/asset/apply.js";
5
- import assetDelete from "./src/commands/asset/delete.js";
6
- import bucketOrm from "./src/commands/bucket/orm.js";
7
- import contextLs from "./src/commands/context/ls.js";
8
- import contextRemove from "./src/commands/context/remove.js";
9
- import contextSet from "./src/commands/context/set.js";
10
- import contextSwitch from "./src/commands/context/switch.js";
11
- import functionOrm from "./src/commands/function/orm.js";
12
- import projectLs from "./src/commands/project/ls.js";
13
- import projectRemove from "./src/commands/project/remove.js";
14
- import projectStart from "./src/commands/project/start.js";
15
- import projectSync from "./src/commands/project/sync.js";
16
- import projectUpgrade from "./src/commands/project/upgrade.js";
17
- import pkg from "./package.json" with { type: "json" };
18
- export function run(argv) {
19
- program
20
- .version(pkg.version)
21
- .name("spica")
22
- .bin("spica")
23
- .description("Command line interface for spica.");
24
- assetApply(program);
25
- assetDelete(program);
26
- bucketOrm(program);
27
- contextLs(program);
28
- contextRemove(program);
29
- contextSet(program);
30
- contextSwitch(program);
31
- functionOrm(program);
32
- projectLs(program);
33
- projectRemove(program);
34
- projectStart(program);
35
- projectSync(program);
36
- projectUpgrade(program);
37
- return program.run(argv);
38
- }
39
- //# sourceMappingURL=index.js.map
1835
+ import getport from "get-port";
1836
+ import open from "open";
1837
+ import path8 from "path";
1838
+ import fs8 from "fs";
1839
+
1840
+ // apps/cli/src/project.ts
1841
+ import docker3 from "dockerode";
1842
+ import semver from "semver";
1843
+ var machine;
1844
+ var DockerMachine = class extends docker3 {
1845
+ constructor() {
1846
+ if (!machine) {
1847
+ super();
1848
+ machine = this;
1849
+ }
1850
+ return machine;
1851
+ }
1852
+ doesImageExist(image, tag) {
1853
+ return machine.listImages({
1854
+ filters: JSON.stringify({ reference: [`${image}:${tag}`] })
1855
+ }).then((images) => images.length > 0);
1856
+ }
1857
+ pullImage(image, tag) {
1858
+ return new Promise(
1859
+ (resolve, reject) => machine.pull(`${image}:${tag}`, {}, function(err, stream) {
1860
+ if (err) {
1861
+ if (err.message && err.message.indexOf(`manifest for ${image}`)) {
1862
+ reject(new ImageNotFoundError(image, tag));
1863
+ } else {
1864
+ reject(err);
1865
+ }
1866
+ } else {
1867
+ machine.modem.followProgress(stream, resolve);
1868
+ }
1869
+ })
1870
+ );
1871
+ }
1872
+ };
1873
+ var ImageNotFoundError = class extends Error {
1874
+ constructor(image, tag) {
1875
+ super(`Could not find the image ${image}:${tag}.`);
1876
+ }
1877
+ };
1878
+ var version = (image) => {
1879
+ const vers = /^spicaengine\/(spica|api):(.*)/g.exec(image)[2];
1880
+ if (nonActualVersions.includes(vers)) {
1881
+ return machine.getImage(image).inspect().then((info) => {
1882
+ const actualVersion = info.RepoTags.map((tag) => tag.slice(tag.indexOf(":") + 1)).find(
1883
+ (tag) => semver.valid(tag)
1884
+ );
1885
+ if (!actualVersion) {
1886
+ throw Error(`Could not find the actual version of image '${image}'`);
1887
+ }
1888
+ return actualVersion;
1889
+ });
1890
+ }
1891
+ return Promise.resolve(vers);
1892
+ };
1893
+ var nonActualVersions = ["latest"];
1894
+ function isVersionUpgrade(desiredVersion, oldVersion) {
1895
+ if (desiredVersion == "latest") {
1896
+ return true;
1897
+ }
1898
+ return semver.gt(desiredVersion, oldVersion);
1899
+ }
1900
+
1901
+ // apps/cli/src/commands/project/start.ts
1902
+ var { CaporalValidator } = caporalCore;
1903
+ function streamToBuffer(stream) {
1904
+ return new Promise((resolve) => {
1905
+ const response = [];
1906
+ if (process.platform.includes("win")) {
1907
+ stream.once("resume", () => {
1908
+ setTimeout(() => {
1909
+ resolve(Buffer.concat(response));
1910
+ }, 2e3);
1911
+ });
1912
+ }
1913
+ stream.on("data", (chunk) => response.push(chunk));
1914
+ stream.on("end", () => resolve(Buffer.concat(response)));
1915
+ });
1916
+ }
1917
+ async function create({ args: cmdArgs, options }) {
1918
+ const { name } = cmdArgs;
1919
+ const { localResourceFolder } = options;
1920
+ const machine2 = new DockerMachine();
1921
+ const networkName = `${name}-network`, databaseName = `${name}-db`, port = await getport({ port: options.port }), publicHost = `http://localhost:${port}`, apiUrl = `${publicHost}/api`, functionApiUrl = apiUrl.replace("localhost", "host.docker.internal");
1922
+ if (options.port.toString() != port.toString() && options.port != 4500) {
1923
+ console.info(`Port ${options.port} already in use, the port ${port} will be used instead.`);
1924
+ }
1925
+ let identifier = "spica";
1926
+ let password = "spica";
1927
+ let args = [];
1928
+ if (options.apiOptions) {
1929
+ const filename = path8.resolve(options.apiOptions);
1930
+ const rawFile = fs8.readFileSync(filename, { encoding: "utf8" });
1931
+ let apiOptions = {};
1932
+ try {
1933
+ apiOptions = JSON.parse(rawFile);
1934
+ identifier = apiOptions["passport-default-identity-identifier"] || identifier;
1935
+ password = apiOptions["passport-default-identity-password"] || password;
1936
+ } catch (error) {
1937
+ throw Error(`Error while parsing api-options file. ${error}`);
1938
+ }
1939
+ for (const [key, value] of Object.entries(apiOptions)) {
1940
+ args.push(`--${key}=${value}`);
1941
+ }
1942
+ }
1943
+ const persistentPath = "/var/data";
1944
+ args = [
1945
+ ...args,
1946
+ // If user defines some of these values in the apiOptions file, they will be overwriten.
1947
+ // We should explain this behavior to users on the documentation or somewhere else.
1948
+ `--database-name=${name}`,
1949
+ `--database-replica-set=${name}`,
1950
+ `--database-uri="mongodb://${databaseName}-0,${databaseName}-1,${databaseName}-2"`,
1951
+ `--public-url=${apiUrl}`,
1952
+ `--function-api-url=${functionApiUrl}`,
1953
+ `--master-key=${name}`,
1954
+ `--persistent-path=${persistentPath}`
1955
+ ];
1956
+ const foundNetworks = await machine2.listNetworks({
1957
+ filters: JSON.stringify({ label: [`namespace=${name}`] })
1958
+ });
1959
+ const foundContainers = await machine2.listContainers({
1960
+ all: true,
1961
+ filters: JSON.stringify({ label: [`namespace=${name}`] })
1962
+ });
1963
+ if ((foundNetworks.length || foundContainers.length) && !options.force) {
1964
+ console.warn(
1965
+ `There is a instance with name ${name} already exists.
1966
+ Use --force to delete existing one.`
1967
+ );
1968
+ return;
1969
+ }
1970
+ if (options.force && (foundNetworks.length || foundContainers.length)) {
1971
+ await spin({
1972
+ text: `Shutting down and removing the previous containers, networks${!options.retainVolumes ? ", and volumes" : ""}.`,
1973
+ op: async () => {
1974
+ await Promise.all(
1975
+ foundContainers.map(async (containerInfo) => {
1976
+ const container = await machine2.getContainer(containerInfo.Id);
1977
+ await container.remove({
1978
+ v: true,
1979
+ // Remove volumes attached to the container
1980
+ force: true
1981
+ // Stop if the container running
1982
+ });
1983
+ return new Promise((resolve) => setTimeout(resolve, 1e3));
1984
+ })
1985
+ );
1986
+ await Promise.all(foundNetworks.map((network2) => machine2.getNetwork(network2.Id).remove()));
1987
+ if (!options.retainVolumes) {
1988
+ const foundVolumes = (await machine2.listVolumes({
1989
+ filters: JSON.stringify({ label: [`namespace=${name}`] })
1990
+ })).Volumes;
1991
+ await Promise.all(foundVolumes.map((volume) => machine2.getVolume(volume.Name).remove()));
1992
+ }
1993
+ }
1994
+ });
1995
+ }
1996
+ await spin({
1997
+ text: `Pulling images.`,
1998
+ op: async (spinner) => {
1999
+ const images = [
2000
+ {
2001
+ image: "spicaengine/api",
2002
+ tag: options.imageVersion.toString()
2003
+ },
2004
+ {
2005
+ image: "spicaengine/panel",
2006
+ tag: options.imageVersion.toString()
2007
+ },
2008
+ {
2009
+ image: "mongo",
2010
+ tag: options.mongoVersion.toString()
2011
+ },
2012
+ {
2013
+ image: "nginx",
2014
+ tag: "latest"
2015
+ }
2016
+ ];
2017
+ const imagesToPull = [];
2018
+ for (const image of images) {
2019
+ const exists = await machine2.doesImageExist(image.image, image.tag);
2020
+ if (exists && options.imagePullPolicy == "if-not-present") {
2021
+ continue;
2022
+ }
2023
+ imagesToPull.push(image);
2024
+ }
2025
+ let pulled = 0;
2026
+ function increasePulledCount() {
2027
+ pulled++;
2028
+ spinner.text = `Pulling images (${pulled}/${imagesToPull.length})`;
2029
+ }
2030
+ return Promise.all(
2031
+ imagesToPull.map(
2032
+ (image) => machine2.pullImage(image.image, image.tag).then(() => increasePulledCount())
2033
+ )
2034
+ );
2035
+ }
2036
+ });
2037
+ const network = await spin({
2038
+ text: `Creating a network named ${networkName}.`,
2039
+ op: machine2.createNetwork({
2040
+ Name: networkName,
2041
+ Labels: { namespace: name }
2042
+ })
2043
+ });
2044
+ async function createMongoDB(instanceIndex) {
2045
+ const container = await machine2.createContainer({
2046
+ Image: `mongo:${options.mongoVersion.toString()}`,
2047
+ name: `${databaseName}-${instanceIndex}`,
2048
+ Cmd: ["--replSet", name, "--bind_ip_all"],
2049
+ Labels: { namespace: name },
2050
+ HostConfig: {
2051
+ RestartPolicy: {
2052
+ Name: options.restart ? "unless-stopped" : "no"
2053
+ },
2054
+ Mounts: [
2055
+ {
2056
+ Source: `${name}-db-${instanceIndex}`,
2057
+ Type: "volume",
2058
+ Target: "/data/db",
2059
+ VolumeOptions: {
2060
+ DriverConfig: {
2061
+ Name: "local",
2062
+ Options: {}
2063
+ },
2064
+ NoCopy: false,
2065
+ Labels: { namespace: name }
2066
+ }
2067
+ }
2068
+ ]
2069
+ }
2070
+ });
2071
+ await network.connect({ Container: container.id });
2072
+ return container.start();
2073
+ }
2074
+ const databaseReplicas = Number(options.databaseReplicas);
2075
+ let shell = "mongo";
2076
+ async function detectShell(container) {
2077
+ const probe = await container.exec({
2078
+ Cmd: ["sh", "-c", "command -v mongosh || command -v mongo || true"],
2079
+ AttachStdout: true,
2080
+ AttachStderr: true
2081
+ });
2082
+ const res = await probe.start({});
2083
+ const out = (await streamToBuffer(res)).toString().trim();
2084
+ if (!out) throw new Error("no mongo shell available in container");
2085
+ return out.split("/").pop();
2086
+ }
2087
+ await spin({
2088
+ text: `Creating database containers (1/${databaseReplicas})`,
2089
+ op: async (spinner) => {
2090
+ for (let index = 0; index < databaseReplicas; index++) {
2091
+ await createMongoDB(index);
2092
+ spinner.text = `Creating database containers (${index + 1}/${databaseReplicas})`;
2093
+ }
2094
+ }
2095
+ });
2096
+ await spin({
2097
+ text: "Waiting the database containers to become ready.",
2098
+ op: async (spinner) => {
2099
+ const replSetConfig = JSON.stringify({
2100
+ _id: name,
2101
+ members: new Array(databaseReplicas).fill(0).map((_, index) => {
2102
+ return { _id: index, host: `${databaseName}-${index}` };
2103
+ })
2104
+ });
2105
+ const firstContainer = machine2.getContainer(`${databaseName}-0`);
2106
+ const detected = await detectShell(firstContainer);
2107
+ if (detected) shell = detected;
2108
+ const initiateReplication = async (reconfig = false) => {
2109
+ spinner.text = "Initiating replication between database containers.";
2110
+ const exec = await firstContainer.exec({
2111
+ Cmd: [
2112
+ shell,
2113
+ "admin",
2114
+ "--eval",
2115
+ reconfig ? `rs.reconfig(${replSetConfig}, { force: true })` : `rs.initiate(${replSetConfig})`
2116
+ ],
2117
+ AttachStderr: true,
2118
+ AttachStdout: true
2119
+ });
2120
+ const result = await exec.start({});
2121
+ const buffer = await streamToBuffer(result);
2122
+ return buffer.toString();
2123
+ };
2124
+ let output = await initiateReplication();
2125
+ let retry = 0, maxRetries = 5, wait = 1e3;
2126
+ while (retry < maxRetries) {
2127
+ retry++;
2128
+ if (output.indexOf("ECONNREFUSED") != -1) {
2129
+ output = await initiateReplication();
2130
+ } else {
2131
+ break;
2132
+ }
2133
+ await new Promise((resolve) => setTimeout(resolve, wait));
2134
+ spinner.text = `Initiating replication between database containers. Retrying ${retry}`;
2135
+ }
2136
+ if (output.indexOf("already initialized") != -1) {
2137
+ output = await initiateReplication(true);
2138
+ }
2139
+ if (output.indexOf("ok: 1") == -1) {
2140
+ return Promise.reject(output);
2141
+ }
2142
+ }
2143
+ });
2144
+ await spin({
2145
+ text: "Waiting for the replica set to become ready.",
2146
+ op: async () => {
2147
+ const firstContainer = machine2.getContainer(`${databaseName}-0`);
2148
+ for (let i = 0; i < 15; i++) {
2149
+ const exec = await firstContainer.exec({
2150
+ Cmd: [shell, "admin", "--eval", "rs.status()"],
2151
+ AttachStderr: true,
2152
+ AttachStdout: true
2153
+ });
2154
+ const output = await exec.start({});
2155
+ const response = await streamToBuffer(output);
2156
+ const responseText = response.toString("utf-8");
2157
+ if (responseText.indexOf("ok: 1") > -1 && responseText.indexOf("stateStr: 'PRIMARY'") > -1) {
2158
+ return Promise.resolve();
2159
+ }
2160
+ await new Promise((resolve) => setTimeout(resolve, 2e3));
2161
+ }
2162
+ return Promise.reject("Replica Set did not become ready in 30 seconds.");
2163
+ }
2164
+ });
2165
+ let binds = [];
2166
+ if (localResourceFolder) {
2167
+ fs8.mkdirSync(localResourceFolder, { recursive: true });
2168
+ binds = [`${localResourceFolder}:${persistentPath}/representatives`];
2169
+ }
2170
+ await spin({
2171
+ text: `Creating spica containers (0/2)`,
2172
+ op: async (spinner) => {
2173
+ const client = await machine2.createContainer({
2174
+ Image: `spicaengine/panel:${options.imageVersion}`,
2175
+ name: `${name}-spica`,
2176
+ Env: ["BASE_URL=/"],
2177
+ Labels: { namespace: name },
2178
+ HostConfig: {
2179
+ RestartPolicy: {
2180
+ Name: options.restart ? "unless-stopped" : "no"
2181
+ }
2182
+ }
2183
+ });
2184
+ await network.connect({ Container: client.id });
2185
+ await client.start();
2186
+ spinner.text = `Creating spica containers (1/2)`;
2187
+ const api = await machine2.createContainer({
2188
+ Image: `spicaengine/api:${options.imageVersion}`,
2189
+ name: `${name}-api`,
2190
+ Cmd: args,
2191
+ Labels: { namespace: name },
2192
+ ExposedPorts: { "80/tcp": {} },
2193
+ HostConfig: {
2194
+ RestartPolicy: {
2195
+ Name: options.restart ? "unless-stopped" : "no"
2196
+ },
2197
+ Mounts: [
2198
+ {
2199
+ Target: persistentPath,
2200
+ Source: `${name}-api`,
2201
+ Type: "volume",
2202
+ VolumeOptions: {
2203
+ NoCopy: false,
2204
+ Labels: { namespace: name },
2205
+ DriverConfig: {
2206
+ Name: "local",
2207
+ Options: {}
2208
+ }
2209
+ }
2210
+ }
2211
+ ],
2212
+ Binds: binds
2213
+ }
2214
+ });
2215
+ await network.connect({ Container: api.id });
2216
+ await api.start();
2217
+ await new Promise((resolve) => setTimeout(resolve, 2e3));
2218
+ spinner.text = `Creating spica containers (2/2)`;
2219
+ }
2220
+ });
2221
+ await spin({
2222
+ text: `Creating an ingress to route traffic.`,
2223
+ op: async () => {
2224
+ const proxy = await machine2.createContainer({
2225
+ Image: `nginx:latest`,
2226
+ name: `${name}-ingress`,
2227
+ HostConfig: {
2228
+ PortBindings: { "80/tcp": [{ HostPort: port.toString() }] },
2229
+ RestartPolicy: {
2230
+ Name: options.restart ? "unless-stopped" : "no"
2231
+ }
2232
+ },
2233
+ Labels: { namespace: name }
2234
+ });
2235
+ await network.connect({ Container: proxy.id });
2236
+ await proxy.start();
2237
+ const nginxConfig = `
2238
+ upstream ${name}-api {
2239
+ server ${name}-api;
2240
+ }
2241
+ upstream ${name}-spica {
2242
+ server ${name}-spica;
2243
+ }
2244
+ server {
2245
+ listen 80;
2246
+ server_name localhost;
2247
+
2248
+ location ~ ^/api/?(.*) {
2249
+ proxy_pass http://${name}-api/$1$is_args$args;
2250
+ proxy_set_header Host $host;
2251
+ proxy_set_header X-Forwarded-Proto $scheme;
2252
+ proxy_set_header X-Forwarded-Port $server_port;
2253
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
2254
+ proxy_http_version 1.1;
2255
+ proxy_set_header Upgrade $http_upgrade;
2256
+ proxy_set_header Connection "Upgrade";
2257
+ }
2258
+
2259
+ location ~ ^/?(.*) {
2260
+ proxy_pass http://${name}-spica/$1$is_args$args;
2261
+ proxy_set_header Host $host;
2262
+ proxy_set_header X-Forwarded-Proto $scheme;
2263
+ proxy_set_header X-Forwarded-Port $server_port;
2264
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
2265
+ proxy_http_version 1.1;
2266
+ proxy_set_header Upgrade $http_upgrade;
2267
+ proxy_set_header Connection "Upgrade";
2268
+ }
2269
+ }
2270
+ `;
2271
+ const exec = await proxy.exec({
2272
+ Cmd: ["bash", "-c", `echo '${nginxConfig}' > /etc/nginx/conf.d/default.conf`],
2273
+ AttachStderr: true,
2274
+ AttachStdout: true
2275
+ });
2276
+ const output = await exec.start({});
2277
+ await streamToBuffer(output);
2278
+ await proxy.restart();
2279
+ }
2280
+ });
2281
+ console.info(`
2282
+ Spica ${name} is serving on ${publicHost}.
2283
+ Open your browser on ${publicHost} to login.
2284
+
2285
+ Identitifer: ${identifier}
2286
+ Password: ${password}
2287
+ `);
2288
+ if (options.open) {
2289
+ await open(`${publicHost}/spica`);
2290
+ }
2291
+ }
2292
+ function start_default(program2) {
2293
+ return program2.command("project start", "Start a project on your local machine.").argument("<name>", "Name of the project.", { validator: projectName }).option(
2294
+ "-p, --port",
2295
+ "Port that ingress will serve on. If not specified an open port will be used.",
2296
+ {
2297
+ default: "4500",
2298
+ required: true,
2299
+ validator: CaporalValidator.NUMBER
2300
+ }
2301
+ ).option("--image-version", "Version of the spica to run.", {
2302
+ default: "latest",
2303
+ validator: CaporalValidator.STRING
2304
+ }).option("--mongo-version", "Version of the MongoDB image to run.", {
2305
+ default: "8.0",
2306
+ validator: CaporalValidator.STRING
2307
+ }).option("-o, --open", "Open project authorization page after creation.", {
2308
+ validator: CaporalValidator.BOOLEAN
2309
+ }).option("-f, --force", "Remove the existing project if exists with same name.", {
2310
+ validator: CaporalValidator.BOOLEAN
2311
+ }).option("--retain-volumes", "When false, the existing data will be removed.", {
2312
+ default: true,
2313
+ validator: CaporalValidator.BOOLEAN
2314
+ }).option("--restart", "Restart failed containers if exits unexpectedly.", {
2315
+ default: true,
2316
+ validator: CaporalValidator.BOOLEAN
2317
+ }).option(
2318
+ "--image-pull-policy",
2319
+ "Image pull policy. when 'if-not-present' images won't be pulled in if already present on the docker.",
2320
+ {
2321
+ default: "if-not-present",
2322
+ validator: ["if-not-present", "default"]
2323
+ }
2324
+ ).option("--database-replicas", "Number of database nodes.", {
2325
+ default: "1",
2326
+ validator: CaporalValidator.NUMBER
2327
+ }).option(
2328
+ "--api-options",
2329
+ "Absolute file path that contains api key options as key value in JSON format.",
2330
+ {
2331
+ validator: CaporalValidator.STRING
2332
+ }
2333
+ ).option(
2334
+ "--local-resource-folder",
2335
+ `Absolute local folder path to sync resources from and to Spica.
2336
+ WARNING: During container startup, the initial synchronization from Spica to the local folder will remove local files (bucket, function, etc.) and insert Spica ones.
2337
+ Files that aren't managed by Spica will remain(.git, .gitignore etc.).
2338
+ Backup or commit necessary files before starting.`,
2339
+ {
2340
+ validator: projectLocalResourceFolder
2341
+ }
2342
+ ).action(create);
2343
+ }
2344
+
2345
+ // apps/cli/src/commands/project/sync.ts
2346
+ import caporalCore2 from "@caporal/core";
2347
+ import { bold as bold3, green as green3, red as red3 } from "colorette";
2348
+ import lodash from "lodash";
2349
+ var { CaporalValidator: CaporalValidator2 } = caporalCore2;
2350
+ var { isEqual } = lodash;
2351
+ var IGNORE_ERRORS;
2352
+ var CONCURRENCY_LIMIT;
2353
+ async function sync({
2354
+ options: {
2355
+ sourceUrl,
2356
+ sourceApikey,
2357
+ targetUrl,
2358
+ targetApikey,
2359
+ modules,
2360
+ bucketIds,
2361
+ functionIds,
2362
+ apikeyIds,
2363
+ policyIds,
2364
+ envVarIds,
2365
+ dryRun,
2366
+ ignoreErrors,
2367
+ concurrencyLimit
2368
+ }
2369
+ }) {
2370
+ IGNORE_ERRORS = ignoreErrors;
2371
+ CONCURRENCY_LIMIT = concurrencyLimit;
2372
+ const sourceService = httpService.create({
2373
+ baseUrl: sourceUrl.toString(),
2374
+ authorization: `APIKEY ${sourceApikey}`
2375
+ });
2376
+ const targetService = httpService.create({
2377
+ baseUrl: targetUrl.toString(),
2378
+ authorization: `APIKEY ${targetApikey}`
2379
+ });
2380
+ modules = modules.toString().split(",").map((m) => m.trim());
2381
+ const coreSynchronizers = [
2382
+ FunctionSynchronizer,
2383
+ BucketSynchronizer,
2384
+ ApikeySynchronizer,
2385
+ PolicySynchronizer,
2386
+ EnvironmentVariableSynchronizer
2387
+ ];
2388
+ const synchronizers = [];
2389
+ for (const Ctor of coreSynchronizers) {
2390
+ const synchronizer = new Ctor(sourceService, targetService, {
2391
+ bucketIds: transformIDs(bucketIds),
2392
+ functionIds: transformIDs(functionIds),
2393
+ apikeyIds: transformIDs(apikeyIds),
2394
+ policyIds: transformIDs(policyIds),
2395
+ envVarIds: transformIDs(envVarIds)
2396
+ });
2397
+ const subSynchronizers = await synchronizer.initialize().catch((e) => {
2398
+ return Promise.reject(returnErrorMessage(e));
2399
+ });
2400
+ synchronizers.push(synchronizer);
2401
+ synchronizers.push(...subSynchronizers);
2402
+ }
2403
+ for (const name of modules) {
2404
+ const moduleSynchronizers = synchronizers.filter((s) => s.moduleName == name);
2405
+ for (const synchronizer of moduleSynchronizers) {
2406
+ const { insertions, updations, deletions } = await synchronizer.analyze().catch((e) => {
2407
+ return Promise.reject(returnErrorMessage(e));
2408
+ });
2409
+ if (dryRun) {
2410
+ printActions({
2411
+ insertions,
2412
+ updations,
2413
+ deletions,
2414
+ field: synchronizer.primaryField,
2415
+ moduleName: synchronizer.getDisplayableModuleName()
2416
+ });
2417
+ } else {
2418
+ try {
2419
+ await synchronizer.synchronize();
2420
+ } catch (error) {
2421
+ Promise.reject(returnErrorMessage(error));
2422
+ }
2423
+ console.log(
2424
+ `
2425
+ ${synchronizer.getDisplayableModuleName()} synchronization has been completed!`.toUpperCase()
2426
+ );
2427
+ }
2428
+ }
2429
+ }
2430
+ }
2431
+ function sync_default(program2) {
2432
+ return program2.command(
2433
+ "project sync",
2434
+ `Synchronize selected module objects between two spica instances(local or remote).
2435
+ ${red3(
2436
+ "ATTENTION"
2437
+ )}: Source and target instance versions must be minimum v0.9.19 and for the best results both instance versions should be the same.
2438
+ Also this command will perform adding, overwriting and removing actions of the target instance and it's irreversible.
2439
+ We highly recommend you to use --dry-run=true and check the changes that will be applied before start.`
2440
+ ).option("--source-url", "API address of the instance where objects will be synchronized from", {
2441
+ required: true,
2442
+ validator: CaporalValidator2.STRING
2443
+ }).option("--source-apikey", "Apikey of the instance where objects will be synchronized from", {
2444
+ required: true,
2445
+ validator: CaporalValidator2.STRING
2446
+ }).option("--target-url", "API address of the instance where objects will be synchronized to", {
2447
+ required: true,
2448
+ validator: CaporalValidator2.STRING
2449
+ }).option(
2450
+ "--target-apikey",
2451
+ "API address of the instance where objects will be synchronized to",
2452
+ {
2453
+ required: true,
2454
+ validator: CaporalValidator2.STRING
2455
+ }
2456
+ ).option(
2457
+ "--modules",
2458
+ `Module names of objects that will be synchronized. Available modules: ${green3(
2459
+ availableSyncModules.join(",")
2460
+ )}`,
2461
+ {
2462
+ required: true,
2463
+ validator: validateSyncModules
2464
+ }
2465
+ ).option("--bucket-ids <ids>", "Buckets that will be synchronized (comma-separated)", {
2466
+ required: false,
2467
+ validator: validateSyncIds
2468
+ }).option("--function-ids <ids>", "Functions that will be synchronized (comma-separated)", {
2469
+ required: false,
2470
+ validator: validateSyncIds
2471
+ }).option("--apikey-ids <ids>", "Apikeys that will be synchronized (comma-separated)", {
2472
+ required: false,
2473
+ validator: validateSyncIds
2474
+ }).option("--policy-ids <ids>", "Policies that will be synchronized (comma-separated)", {
2475
+ required: false,
2476
+ validator: validateSyncIds
2477
+ }).option(
2478
+ "--env-var-ids <ids>",
2479
+ "Environment variables that will be synchronized (comma-separated)",
2480
+ {
2481
+ required: false,
2482
+ validator: validateSyncIds
2483
+ }
2484
+ ).option("--dry-run", "Shows the changes that will be applied to the target instance.", {
2485
+ default: false
2486
+ }).option(
2487
+ "--ignore-errors",
2488
+ "Set true if you don't want to interrupt sync process because of failed requests.",
2489
+ {
2490
+ default: false
2491
+ }
2492
+ ).option(
2493
+ "--concurrency-limit",
2494
+ "Increase(if you want to speed up), decrease(if you get some error like ECONNRESET) this value to adjust how many parallel requests that will be sent to the target instance",
2495
+ {
2496
+ default: 100,
2497
+ validator: CaporalValidator2.NUMBER
2498
+ }
2499
+ ).action(sync);
2500
+ }
2501
+ var ResourceGroupComparisor = class {
2502
+ constructor(sources, targets, uniqueField = "_id", ignoredFields = []) {
2503
+ this.sources = sources;
2504
+ this.targets = targets;
2505
+ this.uniqueField = uniqueField;
2506
+ this.ignoredFields = ignoredFields;
2507
+ this.existings = [];
2508
+ this.existingIds = [];
2509
+ this.existings = targets.filter(
2510
+ (target) => sources.some((source) => source[this.uniqueField] == target[this.uniqueField])
2511
+ );
2512
+ this.existingIds = this.existings.map((existing) => existing[this.uniqueField]);
2513
+ }
2514
+ updations() {
2515
+ const updations = [];
2516
+ for (const existing of this.existings) {
2517
+ const source = this.sources.find(
2518
+ (source2) => source2[this.uniqueField] == existing[this.uniqueField]
2519
+ );
2520
+ if (this.ignoredFields.length) {
2521
+ this.ignoredFields.forEach((field) => {
2522
+ delete source[field];
2523
+ delete existing[field];
2524
+ });
2525
+ }
2526
+ if (!isEqual(source, existing)) {
2527
+ updations.push(source);
2528
+ }
2529
+ }
2530
+ return updations;
2531
+ }
2532
+ insertions() {
2533
+ return this.sources.filter((source) => this.existingIds.indexOf(source[this.uniqueField]) == -1);
2534
+ }
2535
+ deletions() {
2536
+ return this.targets.filter((target) => this.existingIds.indexOf(target[this.uniqueField]) == -1);
2537
+ }
2538
+ };
2539
+ var FunctionSynchronizer = class {
2540
+ constructor(sourceService, targetService, options) {
2541
+ this.sourceService = sourceService;
2542
+ this.targetService = targetService;
2543
+ this.options = options;
2544
+ this.moduleName = "function";
2545
+ this.primaryField = "name";
2546
+ this.insertions = [];
2547
+ this.updations = [];
2548
+ this.deletions = [];
2549
+ }
2550
+ async initialize() {
2551
+ const synchronizers = [];
2552
+ const sourceFns = await getFilteredResources(
2553
+ "function",
2554
+ this.sourceService,
2555
+ this.options.functionIds
2556
+ );
2557
+ for (const fn of sourceFns) {
2558
+ synchronizers.push(
2559
+ new FunctionDependencySynchronizer(this.sourceService, this.targetService, fn)
2560
+ );
2561
+ }
2562
+ synchronizers.push(new FunctionIndexSynchronizer(this.sourceService, this.targetService));
2563
+ return synchronizers;
2564
+ }
2565
+ async analyze() {
2566
+ console.log();
2567
+ let sourceFns = await spin({
2568
+ text: "Fetching functions from source instance",
2569
+ op: () => getFilteredResources("function", this.sourceService, this.options.functionIds)
2570
+ });
2571
+ const targetFns = await spin({
2572
+ text: "Fetching functions from target instance",
2573
+ op: () => getFilteredResources("function", this.targetService, this.options.functionIds)
2574
+ });
2575
+ const decider = new ResourceGroupComparisor(sourceFns, targetFns);
2576
+ this.insertions = decider.insertions();
2577
+ this.updations = decider.updations();
2578
+ this.deletions = decider.deletions();
2579
+ return {
2580
+ insertions: this.insertions,
2581
+ updations: this.updations,
2582
+ deletions: this.deletions
2583
+ };
2584
+ }
2585
+ async synchronize() {
2586
+ console.log();
2587
+ const insertPromiseFactories = this.insertions.map(
2588
+ (fn) => () => this.targetService.post("function", fn).catch(
2589
+ (e) => handleRejection({
2590
+ action: "insert",
2591
+ e,
2592
+ objectName: this.moduleName + " " + fn.name
2593
+ })
2594
+ )
2595
+ );
2596
+ await spinUntilPromiseEnd(insertPromiseFactories, "Inserting functions to the target instance");
2597
+ const updatePromiseFactories = this.updations.map(
2598
+ (fn) => () => this.targetService.put(`function/${fn._id}`, fn).catch(
2599
+ (e) => handleRejection({
2600
+ action: "update",
2601
+ e,
2602
+ objectName: this.moduleName + " " + fn.name
2603
+ })
2604
+ )
2605
+ );
2606
+ await spinUntilPromiseEnd(updatePromiseFactories, "Updating target instance functions");
2607
+ const deletePromiseFactories = this.deletions.map(
2608
+ (fn) => () => this.targetService.delete(`function/${fn._id}`).catch(
2609
+ (e) => handleRejection({
2610
+ action: "delete",
2611
+ objectName: this.moduleName + " " + fn.name,
2612
+ e
2613
+ })
2614
+ )
2615
+ );
2616
+ await spinUntilPromiseEnd(deletePromiseFactories, "Deleting target instance functions");
2617
+ }
2618
+ getDisplayableModuleName() {
2619
+ return this.moduleName;
2620
+ }
2621
+ };
2622
+ var FunctionDependencySynchronizer = class {
2623
+ constructor(sourceService, targetService, fn) {
2624
+ this.sourceService = sourceService;
2625
+ this.targetService = targetService;
2626
+ this.fn = fn;
2627
+ this.moduleName = "function";
2628
+ this.subModuleName = "dependency";
2629
+ this.primaryField = "name";
2630
+ this.insertions = [];
2631
+ this.updations = [];
2632
+ this.deletions = [];
2633
+ }
2634
+ initialize() {
2635
+ return Promise.resolve([]);
2636
+ }
2637
+ async analyze() {
2638
+ const deleteTypes = (deps) => deps.map((d) => {
2639
+ delete d.types;
2640
+ return d;
2641
+ });
2642
+ const sourceDeps = await this.sourceService.get(`function/${this.fn._id}/dependencies`).then(deleteTypes);
2643
+ const targetDeps = await this.targetService.get(`function/${this.fn._id}/dependencies`).then(deleteTypes).catch((e) => {
2644
+ if (isNotFoundException(e)) {
2645
+ return [];
2646
+ }
2647
+ return Promise.reject(e.data);
2648
+ });
2649
+ const decider = new ResourceGroupComparisor(sourceDeps, targetDeps, "name");
2650
+ this.insertions = decider.insertions();
2651
+ this.updations = decider.updations();
2652
+ this.deletions = decider.deletions();
2653
+ return {
2654
+ insertions: this.insertions,
2655
+ updations: this.updations,
2656
+ deletions: this.deletions
2657
+ };
2658
+ }
2659
+ async synchronize() {
2660
+ console.log();
2661
+ const promiseFactories = [];
2662
+ const insertBody = [...this.insertions, ...this.updations].reduce(
2663
+ (acc, dep) => {
2664
+ const depName = `${dep.name}@${dep.version.slice(1)}`;
2665
+ acc.name.push(depName);
2666
+ return acc;
2667
+ },
2668
+ { name: [] }
2669
+ );
2670
+ if (insertBody.name.length) {
2671
+ promiseFactories.push(
2672
+ () => this.targetService.post(`function/${this.fn._id}/dependencies`, insertBody).catch((e) => {
2673
+ handleRejection({
2674
+ action: "insert",
2675
+ e,
2676
+ objectName: this.getDisplayableModuleName()
2677
+ });
2678
+ })
2679
+ );
2680
+ }
2681
+ const deletePromiseFactories = this.deletions.map(
2682
+ (dep) => () => this.targetService.delete(`function/${this.fn._id}/dependencies/${dep.name}`).catch(
2683
+ (e) => handleRejection({
2684
+ action: "update",
2685
+ e,
2686
+ objectName: this.getDisplayableModuleName()
2687
+ })
2688
+ )
2689
+ );
2690
+ if (deletePromiseFactories.length) {
2691
+ promiseFactories.push(...deletePromiseFactories);
2692
+ }
2693
+ return spinUntilPromiseEnd(
2694
+ promiseFactories,
2695
+ `Updating function '${this.fn.name}' dependencies`
2696
+ );
2697
+ }
2698
+ getDisplayableModuleName() {
2699
+ return `${this.moduleName} '${this.fn.name}' ${this.subModuleName}`;
2700
+ }
2701
+ };
2702
+ var FunctionIndexSynchronizer = class {
2703
+ constructor(sourceService, targetService) {
2704
+ this.sourceService = sourceService;
2705
+ this.targetService = targetService;
2706
+ this.moduleName = "function";
2707
+ this.subModuleName = "index";
2708
+ this.primaryField = "name";
2709
+ this.insertions = [];
2710
+ this.updations = [];
2711
+ this.deletions = [];
2712
+ }
2713
+ initialize() {
2714
+ return Promise.resolve([]);
2715
+ }
2716
+ async analyze() {
2717
+ const sourceFns = await this.sourceService.get("function");
2718
+ const sourceFnIndexes = await Promise.all(
2719
+ sourceFns.map(
2720
+ (fn) => this.sourceService.get(`function/${fn._id}/index`).then((res) => {
2721
+ return {
2722
+ _id: fn._id,
2723
+ name: fn.name,
2724
+ index: res.index
2725
+ };
2726
+ })
2727
+ )
2728
+ );
2729
+ const targetFnIndexes = await Promise.all(
2730
+ sourceFns.map(
2731
+ (fn) => this.targetService.get(`function/${fn._id}/index`).then((res) => {
2732
+ return {
2733
+ _id: fn._id,
2734
+ name: fn.name,
2735
+ index: res.index
2736
+ };
2737
+ }).catch((e) => {
2738
+ if (isNotFoundException(e)) {
2739
+ return false;
2740
+ }
2741
+ return Promise.reject(e.data);
2742
+ })
2743
+ )
2744
+ ).then((indexes) => indexes.filter(Boolean));
2745
+ const decider = new ResourceGroupComparisor(sourceFnIndexes, targetFnIndexes);
2746
+ this.insertions = decider.insertions();
2747
+ this.updations = decider.updations();
2748
+ this.deletions = decider.deletions();
2749
+ return {
2750
+ insertions: this.insertions,
2751
+ updations: this.updations,
2752
+ deletions: this.deletions
2753
+ };
2754
+ }
2755
+ synchronize() {
2756
+ const promiseFactories = [...this.insertions, ...this.updations].map(
2757
+ (fn) => () => this.targetService.post(`function/${fn._id}/index`, { index: fn.index }).catch(
2758
+ (e) => handleRejection({
2759
+ action: "insert",
2760
+ objectName: this.getDisplayableModuleName() + " " + fn.name,
2761
+ e
2762
+ })
2763
+ )
2764
+ );
2765
+ return spinUntilPromiseEnd(
2766
+ promiseFactories,
2767
+ "Writing indexes to the target instance functions"
2768
+ );
2769
+ }
2770
+ getDisplayableModuleName() {
2771
+ return this.moduleName + " " + this.subModuleName;
2772
+ }
2773
+ };
2774
+ var BucketDataSynchronizer = class {
2775
+ constructor(sourceService, targetService, bucket) {
2776
+ this.sourceService = sourceService;
2777
+ this.targetService = targetService;
2778
+ this.bucket = bucket;
2779
+ this.moduleName = "bucket-data";
2780
+ this.insertions = [];
2781
+ this.updations = [];
2782
+ this.deletions = [];
2783
+ this.primaryField = this.bucket.primary;
2784
+ }
2785
+ initialize() {
2786
+ return Promise.resolve([]);
2787
+ }
2788
+ async analyze() {
2789
+ const params = {
2790
+ localize: false
2791
+ };
2792
+ const sourceData = await this.sourceService.get(`bucket/${this.bucket._id}/data`, {
2793
+ params
2794
+ });
2795
+ const targetData = await this.targetService.get(`bucket/${this.bucket._id}/data`, {
2796
+ params
2797
+ }).catch((e) => {
2798
+ if (isNotFoundException(e)) {
2799
+ return [];
2800
+ }
2801
+ return Promise.reject(e.data);
2802
+ });
2803
+ const decider = new ResourceGroupComparisor(sourceData, targetData);
2804
+ this.insertions = decider.insertions();
2805
+ this.updations = decider.updations();
2806
+ this.deletions = decider.deletions();
2807
+ return {
2808
+ insertions: this.insertions,
2809
+ updations: this.updations,
2810
+ deletions: this.deletions
2811
+ };
2812
+ }
2813
+ async synchronize() {
2814
+ console.log();
2815
+ const insertPromiseFactories = this.insertions.map(
2816
+ (data) => () => this.targetService.post(`bucket/${this.bucket._id}/data`, data).catch(
2817
+ (e) => handleRejection({
2818
+ action: "insert",
2819
+ objectName: this.getDisplayableModuleName() + " " + data[this.primaryField],
2820
+ e
2821
+ })
2822
+ )
2823
+ );
2824
+ await spinUntilPromiseEnd(
2825
+ insertPromiseFactories,
2826
+ `Inserting bucket ${this.bucket.title} data to the target instance`
2827
+ );
2828
+ const updatePromiseFactories = this.updations.map(
2829
+ (data) => () => this.targetService.put(`bucket/${this.bucket._id}/data/${data._id}`, data).catch(
2830
+ (e) => handleRejection({
2831
+ action: "update",
2832
+ e,
2833
+ objectName: this.getDisplayableModuleName() + " " + data[this.primaryField]
2834
+ })
2835
+ )
2836
+ );
2837
+ await spinUntilPromiseEnd(
2838
+ updatePromiseFactories,
2839
+ `Updating bucket ${this.bucket.title} data on the target instance`
2840
+ );
2841
+ const deletePromiseFactories = this.deletions.map(
2842
+ (data) => () => this.targetService.delete(`bucket/${this.bucket._id}/data/${data._id}`).catch(
2843
+ (e) => handleRejection({
2844
+ action: "delete",
2845
+ objectName: this.getDisplayableModuleName() + " " + data[this.primaryField],
2846
+ e
2847
+ })
2848
+ )
2849
+ );
2850
+ await spinUntilPromiseEnd(
2851
+ deletePromiseFactories,
2852
+ `Deleting bucket ${this.bucket.title} data from the target instance`
2853
+ );
2854
+ }
2855
+ getDisplayableModuleName() {
2856
+ return `${this.moduleName} '${this.bucket.title}'`;
2857
+ }
2858
+ };
2859
+ var BucketSynchronizer = class {
2860
+ constructor(sourceService, targetService, options) {
2861
+ this.sourceService = sourceService;
2862
+ this.targetService = targetService;
2863
+ this.options = options;
2864
+ this.moduleName = "bucket";
2865
+ this.primaryField = "title";
2866
+ this.insertions = [];
2867
+ this.updations = [];
2868
+ this.deletions = [];
2869
+ }
2870
+ async initialize() {
2871
+ const synchronizers = [];
2872
+ const sourceBuckets = await getFilteredResources(
2873
+ "bucket",
2874
+ this.sourceService,
2875
+ this.options.bucketIds
2876
+ );
2877
+ for (const bucket of sourceBuckets) {
2878
+ synchronizers.push(
2879
+ new BucketDataSynchronizer(this.sourceService, this.targetService, bucket)
2880
+ );
2881
+ }
2882
+ return synchronizers;
2883
+ }
2884
+ async analyze() {
2885
+ console.log();
2886
+ const sourceBuckets = await spin({
2887
+ text: "Fetching buckets from source instance",
2888
+ op: () => getFilteredResources("bucket", this.sourceService, this.options.bucketIds)
2889
+ });
2890
+ const targetBuckets = await spin({
2891
+ text: "Fetching buckets from target instance",
2892
+ op: () => getFilteredResources("bucket", this.targetService, this.options.bucketIds)
2893
+ });
2894
+ const decider = new ResourceGroupComparisor(sourceBuckets, targetBuckets);
2895
+ this.insertions = decider.insertions();
2896
+ this.updations = decider.updations();
2897
+ this.deletions = decider.deletions();
2898
+ return {
2899
+ insertions: this.insertions,
2900
+ updations: this.updations,
2901
+ deletions: this.deletions
2902
+ };
2903
+ }
2904
+ async synchronize() {
2905
+ console.log();
2906
+ const insertPromiseFactories = this.insertions.map(
2907
+ (bucket) => () => this.targetService.post("bucket", bucket).catch(
2908
+ (e) => handleRejection({
2909
+ action: "insert",
2910
+ objectName: this.getDisplayableModuleName() + " " + bucket.title,
2911
+ e
2912
+ })
2913
+ )
2914
+ );
2915
+ await spinUntilPromiseEnd(insertPromiseFactories, "Inserting buckets to the target instance");
2916
+ const updatePromiseFactories = this.updations.map(
2917
+ (bucket) => () => this.targetService.put(`bucket/${bucket._id}`, bucket).catch(
2918
+ (e) => handleRejection({
2919
+ action: "update",
2920
+ objectName: this.getDisplayableModuleName() + " " + bucket.title,
2921
+ e
2922
+ })
2923
+ )
2924
+ );
2925
+ await spinUntilPromiseEnd(updatePromiseFactories, "Updating buckets on the target instance");
2926
+ const deletePromiseFactories = this.deletions.map(
2927
+ (bucket) => () => this.targetService.delete(`bucket/${bucket._id}`).catch(
2928
+ (e) => handleRejection({
2929
+ action: "delete",
2930
+ objectName: bucket.title,
2931
+ e
2932
+ })
2933
+ )
2934
+ );
2935
+ await spinUntilPromiseEnd(deletePromiseFactories, "Deleting bucket from the target instance");
2936
+ }
2937
+ getDisplayableModuleName() {
2938
+ return this.moduleName;
2939
+ }
2940
+ };
2941
+ var ApikeySynchronizer = class {
2942
+ constructor(sourceService, targetService, options) {
2943
+ this.sourceService = sourceService;
2944
+ this.targetService = targetService;
2945
+ this.options = options;
2946
+ this.moduleName = "apikey";
2947
+ this.primaryField = "name";
2948
+ this.insertions = [];
2949
+ this.updations = [];
2950
+ this.deletions = [];
2951
+ }
2952
+ initialize() {
2953
+ return Promise.resolve([]);
2954
+ }
2955
+ async analyze() {
2956
+ console.log();
2957
+ const sourceApikeys = await spin({
2958
+ text: "Fetching apikeys from source instance",
2959
+ op: () => getFilteredResources("passport/apikey", this.sourceService, this.options.apikeyIds, true)
2960
+ });
2961
+ const targetApikeys = await spin({
2962
+ text: "Fetching apikeys from target instance",
2963
+ op: () => getFilteredResources("passport/apikey", this.targetService, this.options.apikeyIds, true)
2964
+ });
2965
+ const decider = new ResourceGroupComparisor(sourceApikeys, targetApikeys);
2966
+ this.insertions = decider.insertions();
2967
+ this.updations = decider.updations();
2968
+ this.deletions = decider.deletions();
2969
+ return {
2970
+ insertions: this.insertions,
2971
+ updations: this.updations,
2972
+ deletions: this.deletions
2973
+ };
2974
+ }
2975
+ async synchronize() {
2976
+ console.log();
2977
+ const insertPromiseFactories = this.insertions.map((apikey) => () => {
2978
+ const insertRejectionHandler = (e) => handleRejection({
2979
+ action: "insert",
2980
+ objectName: this.getDisplayableModuleName() + " " + apikey.name,
2981
+ e
2982
+ });
2983
+ const policies = [...apikey.policies];
2984
+ delete apikey.policies;
2985
+ const apikeyInsertPromise = this.targetService.post("passport/apikey", apikey).catch((e) => insertRejectionHandler(e));
2986
+ return apikeyInsertPromise.then(() => {
2987
+ const policyAttachPromises = policies.map(
2988
+ (policy) => this.targetService.put(`passport/apikey/${apikey._id}/policy/${policy}`).catch((e) => insertRejectionHandler(e))
2989
+ );
2990
+ return Promise.all(policyAttachPromises);
2991
+ });
2992
+ });
2993
+ await spinUntilPromiseEnd(insertPromiseFactories, "Inserting apikeys to the target instance");
2994
+ const updatePromiseFactories = this.updations.map((apikey) => () => {
2995
+ const rejectionHandler = (e) => {
2996
+ handleRejection({
2997
+ action: "update",
2998
+ objectName: this.getDisplayableModuleName() + " " + apikey.name,
2999
+ e
3000
+ });
3001
+ };
3002
+ const policies = [...apikey.policies];
3003
+ delete apikey.policies;
3004
+ const apikeyDeletePromise = this.targetService.delete(`passport/apikey/${apikey._id}`).catch((e) => rejectionHandler(e));
3005
+ const apikeyInsertPromise = apikeyDeletePromise.then(
3006
+ () => this.targetService.post(`passport/apikey`, apikey).catch((e) => rejectionHandler(e))
3007
+ );
3008
+ return apikeyInsertPromise.then(() => {
3009
+ const policyAttachPromises = policies.map(
3010
+ (policy) => this.targetService.put(`passport/apikey/${apikey._id}/policy/${policy}`).catch((e) => rejectionHandler(e))
3011
+ );
3012
+ return Promise.all(policyAttachPromises);
3013
+ });
3014
+ });
3015
+ await spinUntilPromiseEnd(updatePromiseFactories, "Updating apikeys on the target instance");
3016
+ const deletePromiseFactories = this.deletions.map(
3017
+ (apikey) => () => this.targetService.delete(`passport/apikey/${apikey._id}`).catch(
3018
+ (e) => handleRejection({
3019
+ action: "delete",
3020
+ objectName: apikey.name,
3021
+ e
3022
+ })
3023
+ )
3024
+ );
3025
+ await spinUntilPromiseEnd(deletePromiseFactories, "Deleting apikeys from the target instance");
3026
+ }
3027
+ getDisplayableModuleName() {
3028
+ return this.moduleName;
3029
+ }
3030
+ };
3031
+ var PolicySynchronizer = class {
3032
+ constructor(sourceService, targetService, options) {
3033
+ this.sourceService = sourceService;
3034
+ this.targetService = targetService;
3035
+ this.options = options;
3036
+ this.moduleName = "policy";
3037
+ this.primaryField = "name";
3038
+ this.insertions = [];
3039
+ this.updations = [];
3040
+ this.deletions = [];
3041
+ }
3042
+ initialize() {
3043
+ return Promise.resolve([]);
3044
+ }
3045
+ async analyze() {
3046
+ console.log();
3047
+ const sourcePolicies = await spin({
3048
+ text: "Fetching policies from source instance",
3049
+ op: () => getFilteredResources("passport/policy", this.sourceService, this.options.policyIds, true)
3050
+ });
3051
+ const targetPolicies = await spin({
3052
+ text: "Fetching policies from target instance",
3053
+ op: () => getFilteredResources("passport/policy", this.targetService, this.options.policyIds, true)
3054
+ });
3055
+ const decider = new ResourceGroupComparisor(sourcePolicies, targetPolicies);
3056
+ this.insertions = decider.insertions();
3057
+ this.updations = decider.updations();
3058
+ this.deletions = decider.deletions();
3059
+ return {
3060
+ insertions: this.insertions,
3061
+ updations: this.updations,
3062
+ deletions: this.deletions
3063
+ };
3064
+ }
3065
+ async synchronize() {
3066
+ console.log();
3067
+ const insertPromiseFactories = this.insertions.map((policy) => () => {
3068
+ delete policy.system;
3069
+ return this.targetService.post("passport/policy", policy).catch(
3070
+ (e) => handleRejection({
3071
+ action: "insert",
3072
+ objectName: this.getDisplayableModuleName() + " " + policy.name,
3073
+ e
3074
+ })
3075
+ );
3076
+ });
3077
+ await spinUntilPromiseEnd(insertPromiseFactories, "Inserting policies to the target instance");
3078
+ const updatePromiseFactories = this.updations.map((policy) => () => {
3079
+ delete policy.system;
3080
+ return this.targetService.put(`passport/policy/${policy._id}`, policy).catch(
3081
+ (e) => handleRejection({
3082
+ action: "update",
3083
+ objectName: this.getDisplayableModuleName() + " " + policy.name,
3084
+ e
3085
+ })
3086
+ );
3087
+ });
3088
+ await spinUntilPromiseEnd(updatePromiseFactories, "Updating policies on the target instance");
3089
+ const deletePromiseFactories = this.deletions.map(
3090
+ (policy) => () => this.targetService.delete(`passport/policy/${policy._id}`).catch(
3091
+ (e) => handleRejection({
3092
+ action: "delete",
3093
+ objectName: policy.name,
3094
+ e
3095
+ })
3096
+ )
3097
+ );
3098
+ await spinUntilPromiseEnd(deletePromiseFactories, "Deleting policies from the target instance");
3099
+ }
3100
+ getDisplayableModuleName() {
3101
+ return this.moduleName;
3102
+ }
3103
+ };
3104
+ var EnvironmentVariableSynchronizer = class {
3105
+ constructor(sourceService, targetService, options) {
3106
+ this.sourceService = sourceService;
3107
+ this.targetService = targetService;
3108
+ this.options = options;
3109
+ this.moduleName = "env-var";
3110
+ this.primaryField = "key";
3111
+ this.insertions = [];
3112
+ this.updations = [];
3113
+ this.deletions = [];
3114
+ }
3115
+ initialize() {
3116
+ return Promise.resolve([]);
3117
+ }
3118
+ async analyze() {
3119
+ console.log();
3120
+ const sourceEnvVars = await spin({
3121
+ text: "Fetching env vars from source instance",
3122
+ op: () => getFilteredResources("env-var", this.sourceService, this.options.envVarIds)
3123
+ });
3124
+ const targetEnvVars = await spin({
3125
+ text: "Fetching env vars from target instance",
3126
+ op: () => getFilteredResources("env-var", this.targetService, this.options.envVarIds)
3127
+ });
3128
+ const decider = new ResourceGroupComparisor(sourceEnvVars, targetEnvVars);
3129
+ this.insertions = decider.insertions();
3130
+ this.updations = decider.updations();
3131
+ this.deletions = decider.deletions();
3132
+ return {
3133
+ insertions: this.insertions,
3134
+ updations: this.updations,
3135
+ deletions: this.deletions
3136
+ };
3137
+ }
3138
+ async synchronize() {
3139
+ console.log();
3140
+ const insertPromiseFactories = this.insertions.map(
3141
+ (envVar) => () => this.targetService.post("env-var", envVar).catch(
3142
+ (e) => handleRejection({
3143
+ action: "insert",
3144
+ objectName: this.getDisplayableModuleName() + " " + envVar.key,
3145
+ e
3146
+ })
3147
+ )
3148
+ );
3149
+ await spinUntilPromiseEnd(insertPromiseFactories, "Inserting env vars to the target instance");
3150
+ const updatePromiseFactories = this.updations.map(
3151
+ (envVar) => () => this.targetService.put(`env-var/${envVar._id}`, envVar).catch(
3152
+ (e) => handleRejection({
3153
+ action: "update",
3154
+ objectName: this.getDisplayableModuleName() + " " + envVar.key,
3155
+ e
3156
+ })
3157
+ )
3158
+ );
3159
+ await spinUntilPromiseEnd(updatePromiseFactories, "Updating env vars on the target instance");
3160
+ const deletePromiseFactories = this.deletions.map(
3161
+ (envVar) => () => this.targetService.delete(`env-var/${envVar._id}`).catch(
3162
+ (e) => handleRejection({
3163
+ action: "delete",
3164
+ objectName: envVar.key,
3165
+ e
3166
+ })
3167
+ )
3168
+ );
3169
+ await spinUntilPromiseEnd(deletePromiseFactories, "Deleting env vars from the target instance");
3170
+ }
3171
+ getDisplayableModuleName() {
3172
+ return this.moduleName;
3173
+ }
3174
+ };
3175
+ function printActions({ insertions, updations, deletions, field, moduleName }) {
3176
+ const joinActions = (actions) => {
3177
+ const MAX_LINES = 20;
3178
+ actions = actions.map((a) => `- ${a[field]}`);
3179
+ if (actions.length > MAX_LINES) {
3180
+ actions = actions.slice(0, MAX_LINES);
3181
+ actions.push("...more");
3182
+ }
3183
+ return actions.join("\n");
3184
+ };
3185
+ console.log();
3186
+ console.log(`----- ${moduleName.toUpperCase()} -----`);
3187
+ console.log(
3188
+ `
3189
+ * Found ${bold3(insertions.length)} objects to ${bold3("insert")}:
3190
+ ${joinActions(insertions)}`
3191
+ );
3192
+ console.log(
3193
+ `
3194
+ * Found ${bold3(updations.length)} objects to ${bold3("update")}:
3195
+ ${joinActions(updations)}`
3196
+ );
3197
+ console.log(
3198
+ `
3199
+ * Found ${bold3(deletions.length)} objects to ${bold3("delete")}:
3200
+ ${joinActions(deletions)}`
3201
+ );
3202
+ }
3203
+ function spinUntilPromiseEnd(promiseFactories, label) {
3204
+ if (!promiseFactories.length) {
3205
+ return;
3206
+ }
3207
+ return spin({
3208
+ text: label,
3209
+ op: async (spinner) => {
3210
+ let progress = 0;
3211
+ let count = 0;
3212
+ const concurrency = Math.min(promiseFactories.length, CONCURRENCY_LIMIT);
3213
+ let batchPromises = [];
3214
+ for (let i = 0; i < promiseFactories.length; i++) {
3215
+ batchPromises.push(promiseFactories[i]);
3216
+ if (batchPromises.length == concurrency || i == promiseFactories.length - 1) {
3217
+ await Promise.all(batchPromises.map((b) => b()));
3218
+ batchPromises = [];
3219
+ }
3220
+ count++;
3221
+ progress = 100 / promiseFactories.length * count;
3222
+ spinner.text = `${label} (%${Math.round(progress)})`;
3223
+ }
3224
+ return;
3225
+ }
3226
+ });
3227
+ }
3228
+ function handleRejection({ action, objectName, e }) {
3229
+ const errorMessage = returnErrorMessage(e);
3230
+ const msg = `
3231
+ Failed to ${action} ${bold3(objectName)}.
3232
+ ${errorMessage}`;
3233
+ if (IGNORE_ERRORS) {
3234
+ return console.warn(msg);
3235
+ } else {
3236
+ return Promise.reject(msg);
3237
+ }
3238
+ }
3239
+ function returnErrorMessage(e) {
3240
+ return e.data ? e.data.message || e.data : e;
3241
+ }
3242
+ function isNotFoundException(e) {
3243
+ const code = 404;
3244
+ return e.status == code || e.statusCode == code || e.data && e.data.statusCode == code;
3245
+ }
3246
+ async function getFilteredResources(path9, service, ids, paginate) {
3247
+ const res = await service.get(path9);
3248
+ const resources = paginate ? res.data : res;
3249
+ return ids ? resources.filter((resource) => ids.includes(resource._id)) : resources;
3250
+ }
3251
+ function transformIDs(ids) {
3252
+ return ids === "string" ? ids.split(",") : [];
3253
+ }
3254
+
3255
+ // apps/cli/src/commands/project/upgrade.ts
3256
+ import caporalCore3 from "@caporal/core";
3257
+ var { CaporalValidator: CaporalValidator3 } = caporalCore3;
3258
+ async function upgrade({ args, options }) {
3259
+ const name = args.name;
3260
+ const desiredVersion = args.to;
3261
+ const machine2 = new DockerMachine();
3262
+ const projectContainers = await machine2.listContainers({
3263
+ all: true,
3264
+ filters: JSON.stringify({ label: [`namespace=${name}`] })
3265
+ });
3266
+ if (!projectContainers.length) {
3267
+ return console.error(`Project '${name}' does not exist.`);
3268
+ }
3269
+ const oldClient = projectContainers.find((c) => c.Image.startsWith("spicaengine/panel"));
3270
+ if (!oldClient) {
3271
+ return console.error(
3272
+ "Unable to upgrade the version of the project. Make sure that it's running with no issue."
3273
+ );
3274
+ }
3275
+ const oldApi = projectContainers.find((c) => c.Image.startsWith("spicaengine/api"));
3276
+ if (!oldApi) {
3277
+ return console.error(
3278
+ "Unable to upgrade the version of the project. Make sure that it's running with no issue"
3279
+ );
3280
+ }
3281
+ const oldClientVersion = await version(oldClient.Image);
3282
+ if (!isVersionUpgrade(desiredVersion, oldClientVersion)) {
3283
+ return console.error(
3284
+ `Requested version(${desiredVersion}) is not greater than current version(${oldClientVersion}).`
3285
+ );
3286
+ }
3287
+ const oldApiVersion = await version(oldApi.Image);
3288
+ if (!isVersionUpgrade(desiredVersion, oldApiVersion)) {
3289
+ return console.error(
3290
+ `Requested version(${desiredVersion}) is greater than current version(${oldApiVersion}).`
3291
+ );
3292
+ }
3293
+ await spin({
3294
+ text: "Pulling images..",
3295
+ op: async (spinner) => {
3296
+ const images = [
3297
+ {
3298
+ image: "spicaengine/api",
3299
+ tag: desiredVersion
3300
+ },
3301
+ {
3302
+ image: "spicaengine/panel",
3303
+ tag: desiredVersion
3304
+ }
3305
+ ];
3306
+ images.push({
3307
+ image: "spicaengine/migrate",
3308
+ tag: desiredVersion
3309
+ });
3310
+ let pulled = 0;
3311
+ function increasePulledCount() {
3312
+ pulled++;
3313
+ spinner.text = `Pulling images (${pulled}/${images.length})`;
3314
+ }
3315
+ return Promise.all(
3316
+ images.map(
3317
+ (image) => machine2.pullImage(image.image, image.tag).then(() => increasePulledCount())
3318
+ )
3319
+ );
3320
+ }
3321
+ });
3322
+ const commands = oldApi ? oldApi.Command.split(" ") : [];
3323
+ await spin({
3324
+ text: "Shutting down and removing the old containers..",
3325
+ op: async () => {
3326
+ await Promise.all(
3327
+ [oldClient, oldApi].map(async (containerInfo) => {
3328
+ if (!containerInfo) {
3329
+ return;
3330
+ }
3331
+ const container = await machine2.getContainer(containerInfo.Id);
3332
+ await container.remove({
3333
+ v: true,
3334
+ // Remove volumes attached to the container
3335
+ force: true
3336
+ // Stop if the container running
3337
+ });
3338
+ return new Promise((resolve) => setTimeout(resolve, 1e3));
3339
+ })
3340
+ );
3341
+ }
3342
+ });
3343
+ const network = await machine2.listNetworks({
3344
+ filters: JSON.stringify({ label: [`namespace=${name}`] })
3345
+ }).then(([networkInfo]) => machine2.getNetwork(networkInfo.Id));
3346
+ await spin({
3347
+ text: `Creating spica containers (0/2)`,
3348
+ op: async (spinner) => {
3349
+ const client = await machine2.createContainer({
3350
+ Image: `spicaengine/panel:${desiredVersion}`,
3351
+ name: `${name}-spica`,
3352
+ Env: ["BASE_URL=/"],
3353
+ Labels: { namespace: name },
3354
+ HostConfig: {
3355
+ RestartPolicy: {
3356
+ Name: options.restart ? "unless-stopped" : "no"
3357
+ }
3358
+ }
3359
+ });
3360
+ await network.connect({ Container: client.id });
3361
+ await client.start();
3362
+ spinner.text = `Creating spica containers (1/2)`;
3363
+ const api = await machine2.createContainer({
3364
+ Image: `spicaengine/api:${desiredVersion}`,
3365
+ name: `${name}-api`,
3366
+ Cmd: commands,
3367
+ Labels: { namespace: name },
3368
+ ExposedPorts: { "80/tcp": {} },
3369
+ HostConfig: {
3370
+ RestartPolicy: {
3371
+ Name: options.restart ? "unless-stopped" : "no"
3372
+ },
3373
+ Mounts: [
3374
+ {
3375
+ Target: "/var/data",
3376
+ Source: `${name}-api`,
3377
+ Type: "volume",
3378
+ VolumeOptions: {
3379
+ NoCopy: false,
3380
+ Labels: { namespace: name },
3381
+ DriverConfig: {
3382
+ Name: "local",
3383
+ Options: {}
3384
+ }
3385
+ }
3386
+ }
3387
+ ]
3388
+ }
3389
+ });
3390
+ await network.connect({ Container: api.id });
3391
+ await api.start();
3392
+ await new Promise((resolve) => setTimeout(resolve, 2e3));
3393
+ spinner.text = `Creating spica containers (2/2)`;
3394
+ }
3395
+ });
3396
+ await spin({
3397
+ text: "Migrating existing data to the new version.",
3398
+ op: async (_) => {
3399
+ const existingMigrate = projectContainers.find(
3400
+ (p) => p.Image.startsWith("spicaengine/migrate")
3401
+ );
3402
+ if (existingMigrate) {
3403
+ await machine2.getContainer(existingMigrate.Id).remove();
3404
+ }
3405
+ const databaseUriCommand = commands.find((c) => c.startsWith("--database-uri="));
3406
+ const databaseNameCommand = commands.find((c) => c.startsWith("--database-name="));
3407
+ const migrate = await machine2.createContainer({
3408
+ Image: `spicaengine/migrate:${desiredVersion}`,
3409
+ name: `${name}-migrate`,
3410
+ Labels: { namespace: name },
3411
+ Cmd: [
3412
+ `--from=${oldApiVersion}`,
3413
+ `--to=${desiredVersion}`,
3414
+ databaseUriCommand,
3415
+ databaseNameCommand,
3416
+ "--continue-if-versions-are-equal=true"
3417
+ ],
3418
+ HostConfig: {
3419
+ RestartPolicy: {
3420
+ Name: "on-failure",
3421
+ MaximumRetryCount: 3
3422
+ }
3423
+ }
3424
+ });
3425
+ await network.connect({ Container: migrate.id });
3426
+ await migrate.start();
3427
+ }
3428
+ });
3429
+ console.info(`Project '${name}' version has been set to the version ${desiredVersion}.`);
3430
+ }
3431
+ function upgrade_default(program2) {
3432
+ return program2.command("project upgrade", "Upgrade the version of existing local project.").argument("<name>", "Name of the project.", { validator: projectName }).argument("<to>", "Version of the spica.").option("--restart", "Restart failed containers if exits unexpectedly.", {
3433
+ default: true,
3434
+ validator: CaporalValidator3.BOOLEAN
3435
+ }).action(upgrade);
3436
+ }
3437
+
3438
+ // apps/cli/package.json
3439
+ var package_default = {
3440
+ name: "@spica/cli",
3441
+ version: "0.0.0-PLACEHOLDER",
3442
+ type: "module",
3443
+ exports: {
3444
+ ".": {
3445
+ types: "./index.ts",
3446
+ default: "./dist/index.js"
3447
+ },
3448
+ "./src/commands/project/sync": {
3449
+ types: "./src/commands/project/sync.ts",
3450
+ default: "./dist/src/commands/project/sync.js"
3451
+ },
3452
+ "./src/compile": {
3453
+ types: "./src/compile.ts",
3454
+ default: "./dist/src/compile.js"
3455
+ },
3456
+ "./src/function/triggers/http/services": {
3457
+ types: "./src/function/triggers/http/services/index.ts",
3458
+ default: "./dist/src/function/triggers/http/services/index.js"
3459
+ },
3460
+ "./src/function/triggers": {
3461
+ types: "./src/function/triggers/index.ts",
3462
+ default: "./dist/src/function/triggers/index.js"
3463
+ }
3464
+ },
3465
+ publishConfig: {
3466
+ access: "public"
3467
+ },
3468
+ bin: {
3469
+ spica: "bin/spica"
3470
+ },
3471
+ dependencies: {
3472
+ "@caporal/core": "^2.0.7",
3473
+ axios: "^1.8.2",
3474
+ colorette: "^2.0.20",
3475
+ columnify: "^1.6.0",
3476
+ cosmiconfig: "^9.0.0",
3477
+ dockerode: "^4.0.4",
3478
+ dotenv: "^16.4.7",
3479
+ "get-port": "^7.1.0",
3480
+ jsonpath: "^1.1.1",
3481
+ open: "^10.1.0",
3482
+ ora: "^8.2.0",
3483
+ "pretty-ms": "^9.2.0",
3484
+ semver: "^7.7.1",
3485
+ typescript: "^5.7.3",
3486
+ yaml: "^2.7.1"
3487
+ },
3488
+ devDependencies: {
3489
+ "@types/dockerode": "^3.3.34",
3490
+ "@types/json-schema": "^7.0.15",
3491
+ "@types/node": "^22.10.7",
3492
+ "@types/semver": "^7.5.8"
3493
+ }
3494
+ };
3495
+
3496
+ // apps/cli/index.ts
3497
+ var { program } = caporalCore4;
3498
+ function run(argv) {
3499
+ program.version(package_default.version).name("spica").bin("spica").description("Command line interface for spica.");
3500
+ apply_default(program);
3501
+ delete_default(program);
3502
+ orm_default(program);
3503
+ ls_default(program);
3504
+ remove_default(program);
3505
+ set_default(program);
3506
+ switch_default(program);
3507
+ orm_default2(program);
3508
+ ls_default2(program);
3509
+ remove_default2(program);
3510
+ start_default(program);
3511
+ sync_default(program);
3512
+ upgrade_default(program);
3513
+ return program.run(argv);
3514
+ }
3515
+ export {
3516
+ run
3517
+ };