hereya-cli 0.30.0 → 0.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,379 +1,9 @@
1
- import { randomUUID } from 'node:crypto';
2
- import fs from 'node:fs/promises';
3
1
  import os from 'node:os';
4
2
  import path from 'node:path';
5
- import { getAnyPath } from '../lib/filesystem.js';
6
- import { load, save } from '../lib/yaml-utils.js';
7
- import { WorkspaceSchema, } from './common.js';
8
- export class LocalBackend {
9
- async addPackageToWorkspace(input) {
10
- const workspace$ = await this.getWorkspace(input.workspace);
11
- if (!workspace$.found) {
12
- return {
13
- reason: `Workspace ${input.workspace} not found`,
14
- success: false,
15
- };
16
- }
17
- if (workspace$.hasError) {
18
- return {
19
- reason: workspace$.error,
20
- success: false,
21
- };
22
- }
23
- const { workspace } = workspace$;
24
- if (workspace.mirrorOf) {
25
- return {
26
- reason: `Cannot add package to mirrored workspace ${input.workspace}`,
27
- success: false,
28
- };
29
- }
30
- workspace.packages = {
31
- ...workspace.packages,
32
- [input.package]: {
33
- parameters: input.parameters,
34
- version: '',
35
- },
36
- };
37
- const newEnv = Object.fromEntries(Object.entries(input.env).map(([key, value]) => [key, `${input.infra}:${value}`]));
38
- workspace.env = {
39
- ...workspace.env,
40
- ...newEnv,
41
- };
42
- try {
43
- await this.saveWorkspace(workspace, input.workspace);
44
- return {
45
- success: true,
46
- workspace,
47
- };
48
- }
49
- catch (error) {
50
- return {
51
- reason: error.message,
52
- success: false,
53
- };
54
- }
55
- }
56
- async createWorkspace(input) {
57
- const workspace$ = await this.getWorkspace(input.name);
58
- if (workspace$.found) {
59
- return workspace$.hasError
60
- ? {
61
- reason: workspace$.error,
62
- success: false,
63
- }
64
- : {
65
- isNew: false,
66
- success: true,
67
- workspace: workspace$.workspace,
68
- };
69
- }
70
- if (input.mirrorOf) {
71
- const mirroredWorkspace$ = await this.getWorkspace(input.mirrorOf);
72
- if (!mirroredWorkspace$.found) {
73
- return {
74
- reason: `Mirrored workspace ${input.mirrorOf} not found`,
75
- success: false,
76
- };
77
- }
78
- if (mirroredWorkspace$.hasError) {
79
- return {
80
- reason: mirroredWorkspace$.error,
81
- success: false,
82
- };
83
- }
84
- }
85
- const workspace = {
86
- id: input.name,
87
- mirrorOf: input.mirrorOf,
88
- name: input.name,
89
- };
90
- try {
91
- await this.saveWorkspace(workspace, input.name);
92
- return {
93
- isNew: true,
94
- success: true,
95
- workspace,
96
- };
97
- }
98
- catch (error) {
99
- return {
100
- reason: error.message,
101
- success: false,
102
- };
103
- }
104
- }
105
- async deleteWorkspace(input) {
106
- const workspace$ = await this.getWorkspace(input.name);
107
- if (!workspace$.found) {
108
- return {
109
- message: `Workspace ${input.name} does not exist`,
110
- success: true,
111
- };
112
- }
113
- if (workspace$.hasError) {
114
- return {
115
- reason: workspace$.error,
116
- success: false,
117
- };
118
- }
119
- const { workspace } = workspace$;
120
- const workspaceNames = await this.listWorkspaces();
121
- const allWorkspaces = await Promise.all(workspaceNames.map(async (workspaceName) => {
122
- const w$ = await this.getWorkspace(workspaceName);
123
- if (!w$.found || w$.hasError) {
124
- throw new Error(`Workspace ${workspaceName} not found or has an error`);
125
- }
126
- return w$.workspace;
127
- }));
128
- if (allWorkspaces.some((w) => w.mirrorOf === input.name)) {
129
- return {
130
- reason: `Cannot delete workspace ${input.name} because it is mirrored by ${allWorkspaces
131
- .filter((w) => w.mirrorOf === input.name)
132
- .map((w) => w.name)
133
- .join(', ')}`,
134
- success: false,
135
- };
136
- }
137
- if (!workspace.mirrorOf && Object.keys(workspace.packages ?? {}).length > 0) {
138
- return {
139
- reason: `Cannot delete workspace ${input.name} because it has packages`,
140
- success: false,
141
- };
142
- }
143
- const workspacePath = await this.getWorkspacePath(input.name);
144
- await fs.rm(workspacePath);
145
- return {
146
- success: true,
147
- };
148
- }
149
- async getProvisioningId(input) {
150
- const idFilePath = await getAnyPath(path.join(os.homedir(), '.hereya', 'provisioning', `${input.logicalId}.yaml`), path.join(os.homedir(), '.hereya', 'provisioning', `${input.logicalId}.yml`));
151
- const { data, found } = await load(idFilePath);
152
- if (!found || !data.id) {
153
- data.id = `p-${randomUUID()}`;
154
- await save(data, idFilePath);
155
- }
156
- return {
157
- id: data.id,
158
- success: true,
159
- };
160
- }
161
- async getState(input) {
162
- const projectStatePath = await this.getProjectStatePath(input);
163
- const { data, found } = await load(projectStatePath);
164
- if (found) {
165
- return {
166
- config: data,
167
- found: true,
168
- };
169
- }
170
- return {
171
- found: false,
172
- };
173
- }
174
- async getWorkspace(workspace) {
175
- const workspacePath = await getAnyPath(path.join(os.homedir(), '.hereya', 'state', 'workspaces', `${workspace}.yaml`), path.join(os.homedir(), '.hereya', 'state', 'workspaces', `${workspace}.yml`));
176
- const { data, error, found } = await load(workspacePath);
177
- if (error) {
178
- return {
179
- error,
180
- found: true,
181
- hasError: true,
182
- };
183
- }
184
- if (!found) {
185
- return {
186
- found: false,
187
- };
188
- }
189
- const workspace$ = WorkspaceSchema.safeParse(data);
190
- if (!workspace$.success) {
191
- return {
192
- error: workspace$.error.message,
193
- found: true,
194
- hasError: true,
195
- };
196
- }
197
- let mirroredWorkspace;
198
- if (workspace$.data.mirrorOf) {
199
- const mirroredWorkspace$ = await this.getWorkspace(workspace$.data.mirrorOf);
200
- if (!mirroredWorkspace$.found) {
201
- return {
202
- error: `Mirrored workspace ${workspace$.data.mirrorOf} not found`,
203
- found: true,
204
- hasError: true,
205
- };
206
- }
207
- if (mirroredWorkspace$.hasError) {
208
- return {
209
- error: mirroredWorkspace$.error,
210
- found: true,
211
- hasError: true,
212
- };
213
- }
214
- mirroredWorkspace = mirroredWorkspace$.workspace;
215
- }
216
- return {
217
- found: true,
218
- hasError: false,
219
- workspace: mirroredWorkspace
220
- ? {
221
- ...workspace$.data,
222
- env: {
223
- ...mirroredWorkspace.env,
224
- ...workspace$.data.env,
225
- },
226
- packages: {
227
- ...mirroredWorkspace.packages,
228
- },
229
- }
230
- : {
231
- ...workspace$.data,
232
- packages: {
233
- ...workspace$.data.packages,
234
- },
235
- },
236
- };
237
- }
238
- async getWorkspaceEnv(input) {
239
- const workspace$ = await this.getWorkspace(input.workspace);
240
- if (!workspace$.found) {
241
- return {
242
- reason: `Workspace ${input.workspace} not found`,
243
- success: false,
244
- };
245
- }
246
- if (workspace$.hasError) {
247
- return {
248
- reason: workspace$.error,
249
- success: false,
250
- };
251
- }
252
- return {
253
- env: workspace$.workspace.env ?? {},
254
- success: true,
255
- };
256
- }
257
- async init(options) {
258
- return {
259
- project: {
260
- id: options.project,
261
- name: options.project,
262
- },
263
- workspace: {
264
- id: options.workspace,
265
- name: options.workspace,
266
- },
267
- };
268
- }
269
- async listWorkspaces() {
270
- const workspacesPath = path.join(os.homedir(), '.hereya', 'state', 'workspaces');
271
- const workspaces = await fs.readdir(workspacesPath);
272
- return workspaces.map((workspace) => workspace.replace(/\.yaml|\.yml$/, '')).sort();
273
- }
274
- async removePackageFromWorkspace(input) {
275
- const workspace$ = await this.getWorkspace(input.workspace);
276
- if (!workspace$.found) {
277
- return {
278
- reason: `Workspace ${input.workspace} not found`,
279
- success: false,
280
- };
281
- }
282
- if (workspace$.hasError) {
283
- return {
284
- reason: workspace$.error,
285
- success: false,
286
- };
287
- }
288
- const { workspace } = workspace$;
289
- if (workspace.mirrorOf) {
290
- return {
291
- reason: `Cannot remove package from mirrored workspace ${input.workspace}`,
292
- success: false,
293
- };
294
- }
295
- workspace.packages = Object.fromEntries(Object.entries(workspace.packages ?? {}).filter(([key]) => key !== input.package));
296
- workspace.env = Object.fromEntries(Object.entries(workspace.env ?? {}).filter(([key]) => !(key in input.env)));
297
- try {
298
- await this.saveWorkspace(workspace, input.workspace);
299
- return {
300
- success: true,
301
- workspace,
302
- };
303
- }
304
- catch (error) {
305
- return {
306
- reason: error.message,
307
- success: false,
308
- };
309
- }
310
- }
311
- async saveState(config, workspace) {
312
- const projectStatePath = await this.getProjectStatePath({
313
- project: config.project,
314
- workspace: workspace ?? config.workspace,
315
- });
316
- await save({ ...config, workspace: workspace ?? config.workspace }, projectStatePath);
317
- }
318
- async setEnvVar(input) {
319
- const workspace$ = await this.getWorkspace(input.workspace);
320
- if (!workspace$.found) {
321
- return {
322
- reason: `Workspace ${input.workspace} not found`,
323
- success: false,
324
- };
325
- }
326
- if (workspace$.hasError) {
327
- return {
328
- reason: workspace$.error,
329
- success: false,
330
- };
331
- }
332
- const { workspace } = workspace$;
333
- workspace.env = {
334
- ...workspace.env,
335
- [input.name]: input.value,
336
- };
337
- await this.saveWorkspace(workspace, input.workspace);
338
- return {
339
- success: true,
340
- };
341
- }
342
- async unsetEnvVar(input) {
343
- const workspace$ = await this.getWorkspace(input.workspace);
344
- if (!workspace$.found) {
345
- return {
346
- reason: `Workspace ${input.workspace} not found`,
347
- success: false,
348
- };
349
- }
350
- if (workspace$.hasError) {
351
- return {
352
- reason: workspace$.error,
353
- success: false,
354
- };
355
- }
356
- const { workspace } = workspace$;
357
- const value = workspace.env?.[input.name];
358
- if (!value) {
359
- return {
360
- success: true,
361
- };
362
- }
363
- workspace.env = Object.fromEntries(Object.entries(workspace.env ?? {}).filter(([key]) => key !== input.name));
364
- await this.saveWorkspace(workspace, input.workspace);
365
- return {
366
- success: true,
367
- };
368
- }
369
- async getProjectStatePath(input) {
370
- return getAnyPath(path.join(os.homedir(), '.hereya', 'state', 'projects', input.workspace, `${input.project}.yaml`), path.join(os.homedir(), '.hereya', 'state', 'projects', input.workspace, `${input.project}.yml`));
371
- }
372
- async getWorkspacePath(name) {
373
- return getAnyPath(path.join(os.homedir(), '.hereya', 'state', 'workspaces', `${name}.yaml`), path.join(os.homedir(), '.hereya', 'state', 'workspaces', `${name}.yml`));
374
- }
375
- async saveWorkspace(data, name) {
376
- const workspacePath = await this.getWorkspacePath(name);
377
- await save(data, workspacePath);
3
+ import { LocalFileStorage } from './file-storage/local.js';
4
+ import { FileBackend } from './file.js';
5
+ export class LocalFileBackend extends FileBackend {
6
+ constructor(basePath = path.join(os.homedir(), '.hereya')) {
7
+ super(new LocalFileStorage(basePath));
378
8
  }
379
9
  }
@@ -0,0 +1,4 @@
1
+ import { FileBackend } from "./file.js";
2
+ export declare class S3FileBackend extends FileBackend {
3
+ constructor(bucket: string, basePath?: string);
4
+ }
@@ -0,0 +1,7 @@
1
+ import { S3FileStorage } from "./file-storage/s3.js";
2
+ import { FileBackend } from "./file.js";
3
+ export class S3FileBackend extends FileBackend {
4
+ constructor(bucket, basePath = '') {
5
+ super(new S3FileStorage(bucket, basePath));
6
+ }
7
+ }
@@ -0,0 +1,6 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class ConfigGetBackend extends Command {
3
+ static description: string;
4
+ static examples: string[];
5
+ run(): Promise<void>;
6
+ }
@@ -0,0 +1,12 @@
1
+ import { Command } from '@oclif/core';
2
+ import { getCurrentBackendType } from '../../../backend/config.js';
3
+ export default class ConfigGetBackend extends Command {
4
+ static description = 'get the current backend type';
5
+ static examples = [
6
+ '<%= config.bin %> <%= command.id %>',
7
+ ];
8
+ async run() {
9
+ const backendType = await getCurrentBackendType();
10
+ this.log(backendType);
11
+ }
12
+ }
@@ -0,0 +1,9 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class ConfigUseBackend extends Command {
3
+ static args: {
4
+ type: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
5
+ };
6
+ static description: string;
7
+ static examples: string[];
8
+ run(): Promise<void>;
9
+ }
@@ -0,0 +1,20 @@
1
+ import { Args, Command } from '@oclif/core';
2
+ import { setBackendType } from '../../../backend/config.js';
3
+ export default class ConfigUseBackend extends Command {
4
+ static args = {
5
+ type: Args.string({ description: 'type of backend to use. Possible values: s3, local', required: true }),
6
+ };
7
+ static description = 'set the current backend type';
8
+ static examples = [
9
+ '<%= config.bin %> <%= command.id %> s3',
10
+ '<%= config.bin %> <%= command.id %> local',
11
+ ];
12
+ async run() {
13
+ const { args } = await this.parse(ConfigUseBackend);
14
+ const { type } = args;
15
+ if (type !== 's3' && type !== 'local') {
16
+ this.error('Invalid backend type. Possible values: s3, local');
17
+ }
18
+ await setBackendType(type);
19
+ }
20
+ }
@@ -0,0 +1,7 @@
1
+ export declare function getAwsConfigKey(): string;
2
+ export declare function getAwsConfig(): Promise<{
3
+ backendBucket: string;
4
+ terraformStateBucketName: string;
5
+ terraformStateBucketRegion?: string;
6
+ terraformStateLockTableName: string;
7
+ }>;
@@ -0,0 +1,12 @@
1
+ import { GetParameterCommand, SSMClient } from '@aws-sdk/client-ssm';
2
+ const configKey = '/hereya-bootstrap/config';
3
+ export function getAwsConfigKey() {
4
+ return configKey;
5
+ }
6
+ export async function getAwsConfig() {
7
+ const ssmClient = new SSMClient({});
8
+ const ssmParameter = await ssmClient.send(new GetParameterCommand({
9
+ Name: configKey,
10
+ }));
11
+ return JSON.parse(ssmParameter.Parameter?.Value ?? '{}');
12
+ }
@@ -1,6 +1,5 @@
1
1
  import { BootstrapInput, DeployInput, DeployOutput, DestroyInput, DestroyOutput, Infrastructure, ProvisionInput, ProvisionOutput, ResolveEnvInput, ResolveEnvOutput, SaveEnvInput, SaveEnvOutput, StoreEnvInput, StoreEnvOutput, UndeployInput, UndeployOutput, UnstoreEnvInput, UnstoreEnvOutput } from './common.js';
2
2
  export declare class AwsInfrastructure implements Infrastructure {
3
- private configKey;
4
3
  bootstrap(_: BootstrapInput): Promise<void>;
5
4
  deploy(input: DeployInput): Promise<DeployOutput>;
6
5
  destroy(input: DestroyInput): Promise<DestroyOutput>;
@@ -11,5 +10,4 @@ export declare class AwsInfrastructure implements Infrastructure {
11
10
  unbootstrap(_: BootstrapInput): Promise<void>;
12
11
  undeploy(input: UndeployInput): Promise<UndeployOutput>;
13
12
  unstoreEnv(input: UnstoreEnvInput): Promise<UnstoreEnvOutput>;
14
- private getConfig;
15
13
  }
@@ -3,14 +3,28 @@ import { DeleteParameterCommand, GetParameterCommand, PutParameterCommand, SSMCl
3
3
  import { GetCallerIdentityCommand, STSClient } from '@aws-sdk/client-sts';
4
4
  import { randomUUID } from 'node:crypto';
5
5
  import fs from 'node:fs/promises';
6
- import os from 'node:os';
7
- import path from 'node:path';
8
6
  import { getIac } from '../iac/index.js';
9
7
  import { downloadPackage } from '../lib/package/index.js';
10
8
  import { runShell } from '../lib/shell.js';
9
+ import { getAwsConfig, getAwsConfigKey } from './aws-config.js';
10
+ import { getPackageDownloadPath } from './common.js';
11
11
  import { destroyPackage, provisionPackage } from './index.js';
12
12
  export class AwsInfrastructure {
13
- configKey = '/hereya-bootstrap/config';
13
+ // public static configKey = '/hereya-bootstrap/config'
14
+ // public static async getConfig(): Promise<{
15
+ // backendBucket: string
16
+ // terraformStateBucketName: string
17
+ // terraformStateBucketRegion?: string
18
+ // terraformStateLockTableName: string
19
+ // }> {
20
+ // const ssmClient = new SSMClient({})
21
+ // const ssmParameter = await ssmClient.send(
22
+ // new GetParameterCommand({
23
+ // Name: AwsInfrastructure.configKey,
24
+ // }),
25
+ // )
26
+ // return JSON.parse(ssmParameter.Parameter?.Value ?? '{}')
27
+ // }
14
28
  async bootstrap(_) {
15
29
  const stsClient = new STSClient({});
16
30
  const { Account: accountId } = await stsClient.send(new GetCallerIdentityCommand({}));
@@ -22,7 +36,7 @@ export class AwsInfrastructure {
22
36
  throw new Error(output.reason);
23
37
  }
24
38
  const { env } = output;
25
- const key = this.configKey;
39
+ const key = getAwsConfigKey();
26
40
  const ssmClient = new SSMClient({});
27
41
  const value = JSON.stringify(env);
28
42
  await ssmClient.send(new PutParameterCommand({
@@ -41,11 +55,11 @@ export class AwsInfrastructure {
41
55
  return this.provision(input);
42
56
  }
43
57
  async destroy(input) {
44
- const destPath = path.join(os.homedir(), '.hereya', input.id, input.canonicalName);
58
+ const destPath = await getPackageDownloadPath(input);
45
59
  const downloadPath = await downloadPackage(input.pkgUrl, destPath);
46
60
  const region = process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION;
47
61
  const infraConfig = {
48
- ...await this.getConfig(),
62
+ ...await getAwsConfig(),
49
63
  region,
50
64
  };
51
65
  if (!infraConfig.terraformStateBucketName || !infraConfig.terraformStateLockTableName) {
@@ -75,9 +89,9 @@ export class AwsInfrastructure {
75
89
  return { env: output.env, success: true };
76
90
  }
77
91
  async provision(input) {
78
- const destPath = path.join(os.homedir(), '.hereya', input.id, input.canonicalName);
92
+ const destPath = await getPackageDownloadPath(input);
79
93
  const downloadPath = await downloadPackage(input.pkgUrl, destPath);
80
- const config = await this.getConfig();
94
+ const config = await getAwsConfig();
81
95
  const terraformStateBucketRegion = config.terraformStateBucketRegion || process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION;
82
96
  const infraConfig = {
83
97
  ...config,
@@ -220,11 +234,4 @@ export class AwsInfrastructure {
220
234
  }));
221
235
  return { success: true };
222
236
  }
223
- async getConfig() {
224
- const ssmClient = new SSMClient({});
225
- const ssmParameter = await ssmClient.send(new GetParameterCommand({
226
- Name: this.configKey,
227
- }));
228
- return JSON.parse(ssmParameter.Parameter?.Value ?? '{}');
229
- }
230
237
  }
@@ -18,6 +18,9 @@ export interface Infrastructure {
18
18
  undeploy(input: UndeployInput): Promise<UndeployOutput>;
19
19
  unstoreEnv(input: UnstoreEnvInput): Promise<UnstoreEnvOutput>;
20
20
  }
21
+ export declare function getPackageDownloadPath(input: {
22
+ id: string;
23
+ }): Promise<string>;
21
24
  export type BootstrapInput = {
22
25
  force?: boolean;
23
26
  };
@@ -1,3 +1,5 @@
1
+ import os from 'node:os';
2
+ import path from 'node:path';
1
3
  export var InfrastructureType;
2
4
  (function (InfrastructureType) {
3
5
  InfrastructureType["aws"] = "aws";
@@ -5,3 +7,6 @@ export var InfrastructureType;
5
7
  InfrastructureType["gcp"] = "gcp";
6
8
  InfrastructureType["local"] = "local";
7
9
  })(InfrastructureType || (InfrastructureType = {}));
10
+ export async function getPackageDownloadPath(input) {
11
+ return path.join(os.homedir(), '.hereya', 'packages', input.id);
12
+ }
@@ -1,8 +1,7 @@
1
1
  import * as fs from 'node:fs/promises';
2
- import * as os from 'node:os';
3
- import path from 'node:path';
4
2
  import { getIac } from '../iac/index.js';
5
3
  import { downloadPackage } from '../lib/package/index.js';
4
+ import { getPackageDownloadPath, } from './common.js';
6
5
  export class LocalInfrastructure {
7
6
  async bootstrap() {
8
7
  console.log('Bootstrapping local infrastructure');
@@ -16,8 +15,7 @@ export class LocalInfrastructure {
16
15
  return this.provision(input);
17
16
  }
18
17
  async destroy(input) {
19
- // noinspection DuplicatedCode
20
- const destPath = path.join(os.homedir(), '.hereya', 'packages', input.id);
18
+ const destPath = await getPackageDownloadPath(input);
21
19
  const downloadPath = await downloadPackage(input.pkgUrl, destPath);
22
20
  const iac$ = getIac({ type: input.iacType });
23
21
  if (!iac$.supported) {
@@ -39,8 +37,7 @@ export class LocalInfrastructure {
39
37
  return { env: output.env, success: true };
40
38
  }
41
39
  async provision(input) {
42
- // noinspection DuplicatedCode
43
- const destPath = path.join(os.homedir(), '.hereya', 'packages', input.id);
40
+ const destPath = await getPackageDownloadPath(input);
44
41
  const downloadPath = await downloadPackage(input.pkgUrl, destPath);
45
42
  const iac$ = getIac({ type: input.iacType });
46
43
  if (!iac$.supported) {
@@ -4,3 +4,7 @@ export declare function load<T extends object>(file: string): Promise<{
4
4
  error?: string;
5
5
  found: boolean;
6
6
  }>;
7
+ export declare function parseYaml<T extends object>(content: string): Promise<{
8
+ data: T;
9
+ error?: string;
10
+ }>;
@@ -11,17 +11,24 @@ export async function save(content, file) {
11
11
  }
12
12
  }
13
13
  export async function load(file) {
14
- let data = {};
15
- let found = false;
16
14
  try {
17
15
  const content = await readFile(file, { encoding: 'utf8' });
18
- data = parse(content);
19
- found = true;
16
+ const { data, error } = await parseYaml(content);
17
+ return { data, error, found: true };
20
18
  }
21
19
  catch (error) {
22
20
  if (error.code !== 'ENOENT') {
23
21
  return { data: {}, error: `could not load file ${file}: ${error}`, found: true };
24
22
  }
23
+ return { data: {}, found: false };
24
+ }
25
+ }
26
+ export async function parseYaml(content) {
27
+ try {
28
+ const data = parse(content);
29
+ return { data };
30
+ }
31
+ catch (error) {
32
+ return { data: {}, error: `could not parse yaml: ${error}` };
25
33
  }
26
- return { data: data || {}, found };
27
34
  }