hereya-cli 0.31.0 → 0.33.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
  }
@@ -6,10 +6,25 @@ import fs from 'node:fs/promises';
6
6
  import { getIac } from '../iac/index.js';
7
7
  import { downloadPackage } from '../lib/package/index.js';
8
8
  import { runShell } from '../lib/shell.js';
9
+ import { getAwsConfig, getAwsConfigKey } from './aws-config.js';
9
10
  import { getPackageDownloadPath } from './common.js';
10
11
  import { destroyPackage, provisionPackage } from './index.js';
11
12
  export class AwsInfrastructure {
12
- 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
+ // }
13
28
  async bootstrap(_) {
14
29
  const stsClient = new STSClient({});
15
30
  const { Account: accountId } = await stsClient.send(new GetCallerIdentityCommand({}));
@@ -21,7 +36,7 @@ export class AwsInfrastructure {
21
36
  throw new Error(output.reason);
22
37
  }
23
38
  const { env } = output;
24
- const key = this.configKey;
39
+ const key = getAwsConfigKey();
25
40
  const ssmClient = new SSMClient({});
26
41
  const value = JSON.stringify(env);
27
42
  await ssmClient.send(new PutParameterCommand({
@@ -44,7 +59,7 @@ export class AwsInfrastructure {
44
59
  const downloadPath = await downloadPackage(input.pkgUrl, destPath);
45
60
  const region = process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION;
46
61
  const infraConfig = {
47
- ...await this.getConfig(),
62
+ ...await getAwsConfig(),
48
63
  region,
49
64
  };
50
65
  if (!infraConfig.terraformStateBucketName || !infraConfig.terraformStateLockTableName) {
@@ -76,7 +91,7 @@ export class AwsInfrastructure {
76
91
  async provision(input) {
77
92
  const destPath = await getPackageDownloadPath(input);
78
93
  const downloadPath = await downloadPackage(input.pkgUrl, destPath);
79
- const config = await this.getConfig();
94
+ const config = await getAwsConfig();
80
95
  const terraformStateBucketRegion = config.terraformStateBucketRegion || process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION;
81
96
  const infraConfig = {
82
97
  ...config,
@@ -219,11 +234,4 @@ export class AwsInfrastructure {
219
234
  }));
220
235
  return { success: true };
221
236
  }
222
- async getConfig() {
223
- const ssmClient = new SSMClient({});
224
- const ssmParameter = await ssmClient.send(new GetParameterCommand({
225
- Name: this.configKey,
226
- }));
227
- return JSON.parse(ssmParameter.Parameter?.Value ?? '{}');
228
- }
229
237
  }
@@ -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
  }
@@ -558,6 +558,63 @@
558
558
  "index.js"
559
559
  ]
560
560
  },
561
+ "config:get-backend": {
562
+ "aliases": [],
563
+ "args": {},
564
+ "description": "get the current backend type",
565
+ "examples": [
566
+ "<%= config.bin %> <%= command.id %>"
567
+ ],
568
+ "flags": {},
569
+ "hasDynamicHelp": false,
570
+ "hiddenAliases": [],
571
+ "id": "config:get-backend",
572
+ "pluginAlias": "hereya-cli",
573
+ "pluginName": "hereya-cli",
574
+ "pluginType": "core",
575
+ "strict": true,
576
+ "enableJsonFlag": false,
577
+ "isESM": true,
578
+ "relativePath": [
579
+ "dist",
580
+ "commands",
581
+ "config",
582
+ "get-backend",
583
+ "index.js"
584
+ ]
585
+ },
586
+ "config:use-backend": {
587
+ "aliases": [],
588
+ "args": {
589
+ "type": {
590
+ "description": "type of backend to use. Possible values: s3, local",
591
+ "name": "type",
592
+ "required": true
593
+ }
594
+ },
595
+ "description": "set the current backend type",
596
+ "examples": [
597
+ "<%= config.bin %> <%= command.id %> s3",
598
+ "<%= config.bin %> <%= command.id %> local"
599
+ ],
600
+ "flags": {},
601
+ "hasDynamicHelp": false,
602
+ "hiddenAliases": [],
603
+ "id": "config:use-backend",
604
+ "pluginAlias": "hereya-cli",
605
+ "pluginName": "hereya-cli",
606
+ "pluginType": "core",
607
+ "strict": true,
608
+ "enableJsonFlag": false,
609
+ "isESM": true,
610
+ "relativePath": [
611
+ "dist",
612
+ "commands",
613
+ "config",
614
+ "use-backend",
615
+ "index.js"
616
+ ]
617
+ },
561
618
  "env:set": {
562
619
  "aliases": [],
563
620
  "args": {
@@ -1006,5 +1063,5 @@
1006
1063
  ]
1007
1064
  }
1008
1065
  },
1009
- "version": "0.31.0"
1066
+ "version": "0.33.0"
1010
1067
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "hereya-cli",
3
3
  "description": "Infrastructure as Package",
4
- "version": "0.31.0",
4
+ "version": "0.33.0",
5
5
  "author": "Hereya Developers",
6
6
  "bin": {
7
7
  "hereya": "./bin/run.js"