hereya-cli 0.72.1 → 0.73.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.
@@ -0,0 +1,81 @@
1
+ import { Args, Command, Flags } from '@oclif/core';
2
+ import { Listr, ListrLogger, ListrLogLevels } from 'listr2';
3
+ import { getBackend } from '../../backend/index.js';
4
+ import { getExecutorForWorkspace } from '../../executor/context.js';
5
+ import { getConfigManager } from '../../lib/config/index.js';
6
+ import { getLogger, getLogPath, isDebug } from '../../lib/log.js';
7
+ export default class Uninit extends Command {
8
+ static args = {
9
+ project: Args.string({ description: 'project name', required: true }),
10
+ };
11
+ static description = 'Destroy template infrastructure and uninitialize a project.';
12
+ static examples = [
13
+ '<%= config.bin %> <%= command.id %> myProject -w dev',
14
+ ];
15
+ static flags = {
16
+ chdir: Flags.string({
17
+ description: 'directory to run command in',
18
+ required: false,
19
+ }),
20
+ workspace: Flags.string({
21
+ char: 'w',
22
+ description: 'workspace used during init',
23
+ required: true,
24
+ }),
25
+ };
26
+ async run() {
27
+ const { args, flags } = await this.parse(Uninit);
28
+ const projectRootDir = flags.chdir || process.env.HEREYA_PROJECT_ROOT_DIR;
29
+ const task = new Listr([
30
+ {
31
+ async task(ctx) {
32
+ const backend = await getBackend();
33
+ if (!backend.getProjectMetadata) {
34
+ throw new Error('Uninit requires cloud backend. Run `hereya login` first.');
35
+ }
36
+ const result = await backend.getProjectMetadata({ project: args.project });
37
+ if (!result.found) {
38
+ throw new Error(`No template metadata found for project ${args.project}. Was it initialized with a template?`);
39
+ }
40
+ ctx.metadata = result.metadata;
41
+ },
42
+ title: 'Fetching project metadata',
43
+ },
44
+ {
45
+ rendererOptions: { persistentOutput: isDebug() },
46
+ async task(ctx, task) {
47
+ const executor$ = await getExecutorForWorkspace(flags.workspace, args.project);
48
+ if (!executor$.success)
49
+ throw new Error(executor$.reason);
50
+ const destroyOutput = await executor$.executor.destroy({
51
+ logger: getLogger(task),
52
+ package: ctx.metadata.template,
53
+ project: args.project,
54
+ workspace: flags.workspace,
55
+ });
56
+ if (!destroyOutput.success)
57
+ throw new Error(destroyOutput.reason);
58
+ },
59
+ title: 'Destroying template infrastructure',
60
+ },
61
+ {
62
+ async task() {
63
+ const backend = await getBackend();
64
+ await backend.deleteState({ project: args.project, workspace: flags.workspace });
65
+ const configManager = getConfigManager();
66
+ await configManager.deleteConfig({ projectRootDir });
67
+ },
68
+ title: 'Cleaning up project state',
69
+ },
70
+ ], { concurrent: false });
71
+ try {
72
+ await task.run();
73
+ const myLogger = new ListrLogger({ useIcons: false });
74
+ myLogger.log(ListrLogLevels.COMPLETED, `Template infrastructure destroyed for ${args.project}`);
75
+ myLogger.log(ListrLogLevels.COMPLETED, 'Complete cleanup on Hereya Cloud dashboard.');
76
+ }
77
+ catch (error) {
78
+ this.error(`${error.message}\n\nSee ${getLogPath()} for more details`);
79
+ }
80
+ }
81
+ }
@@ -1,6 +1,10 @@
1
1
  import { IPackageMetadata } from '../package/index.js';
2
+ export interface DeleteConfigInput {
3
+ projectRootDir?: string;
4
+ }
2
5
  export interface ConfigManager {
3
6
  addPackage: (input: AddPackageInput) => Promise<void>;
7
+ deleteConfig: (input: DeleteConfigInput) => Promise<void>;
4
8
  loadConfig: (input: LoadConfigInput) => Promise<LoadConfigOutput>;
5
9
  removePackage: (input: RemovePackageInput) => Promise<void>;
6
10
  saveConfig: (input: SaveConfigInput) => Promise<void>;
@@ -1,6 +1,7 @@
1
- import { AddPackageInput, ConfigManager, LoadConfigInput, LoadConfigOutput, RemovePackageInput, SaveConfigInput } from './common.js';
1
+ import { AddPackageInput, ConfigManager, DeleteConfigInput, LoadConfigInput, LoadConfigOutput, RemovePackageInput, SaveConfigInput } from './common.js';
2
2
  export declare class SimpleConfigManager implements ConfigManager {
3
3
  addPackage(input: AddPackageInput): Promise<void>;
4
+ deleteConfig(input: DeleteConfigInput): Promise<void>;
4
5
  loadConfig(input: LoadConfigInput): Promise<LoadConfigOutput>;
5
6
  removePackage(input: RemovePackageInput): Promise<void>;
6
7
  saveConfig(input: SaveConfigInput): Promise<void>;
@@ -1,5 +1,6 @@
1
+ import { unlink } from 'node:fs/promises';
1
2
  import path from 'node:path';
2
- import { getAnyPath } from '../filesystem.js';
3
+ import { fileExists, getAnyPath } from '../filesystem.js';
3
4
  import * as yaml from '../yaml-utils.js';
4
5
  export class SimpleConfigManager {
5
6
  async addPackage(input) {
@@ -46,6 +47,12 @@ export class SimpleConfigManager {
46
47
  ...sectionUpdate,
47
48
  }, await this.getConfigPath(input.projectRootDir));
48
49
  }
50
+ async deleteConfig(input) {
51
+ const configPath = await this.getConfigPath(input.projectRootDir);
52
+ if (await fileExists(configPath)) {
53
+ await unlink(configPath);
54
+ }
55
+ }
49
56
  async loadConfig(input) {
50
57
  const configFilePath = await this.getConfigPath(input.projectRootDir);
51
58
  const { data: config, found } = await yaml.load(configFilePath);
@@ -1,4 +1,8 @@
1
1
  export declare const gitUtils: {
2
+ /**
3
+ * Clones a git repository using the hereya credential helper.
4
+ */
5
+ cloneWithCredentialHelper(input: CloneWithCredentialHelperInput): Promise<CloneOutput>;
2
6
  /**
3
7
  * Gets the current git branch name.
4
8
  * @param cwd The working directory to execute git command in
@@ -26,4 +30,29 @@ export declare const gitUtils: {
26
30
  * @returns The sanitized branch name
27
31
  */
28
32
  sanitizeBranchName(branch: string): string;
33
+ /**
34
+ * Sets up the hereya credential helper in a git repository.
35
+ */
36
+ setupCredentialHelper(input: {
37
+ hereyaBinPath: string;
38
+ projectDir: string;
39
+ }): Promise<void>;
40
+ };
41
+ export type CloneWithCredentialHelperInput = {
42
+ gitUrl: string;
43
+ hereyaBinPath: string;
44
+ password?: string;
45
+ targetDir: string;
46
+ username?: string;
47
+ };
48
+ export type CloneOutput = {
49
+ reason: string;
50
+ success: false;
51
+ } | {
52
+ success: true;
29
53
  };
54
+ export declare function cloneWithCredentialHelper(input: CloneWithCredentialHelperInput): Promise<CloneOutput>;
55
+ export declare function setupCredentialHelper(input: {
56
+ hereyaBinPath: string;
57
+ projectDir: string;
58
+ }): Promise<void>;
@@ -1,5 +1,12 @@
1
+ import { spawn } from 'node:child_process';
1
2
  import { runShell } from './shell.js';
2
3
  export const gitUtils = {
4
+ /**
5
+ * Clones a git repository using the hereya credential helper.
6
+ */
7
+ async cloneWithCredentialHelper(input) {
8
+ return cloneWithCredentialHelper(input);
9
+ },
3
10
  /**
4
11
  * Gets the current git branch name.
5
12
  * @param cwd The working directory to execute git command in
@@ -58,5 +65,67 @@ export const gitUtils = {
58
65
  */
59
66
  sanitizeBranchName(branch) {
60
67
  return branch.replaceAll(/[^a-zA-Z0-9_-]/g, '');
61
- }
68
+ },
69
+ /**
70
+ * Sets up the hereya credential helper in a git repository.
71
+ */
72
+ async setupCredentialHelper(input) {
73
+ return setupCredentialHelper(input);
74
+ },
62
75
  };
76
+ export async function cloneWithCredentialHelper(input) {
77
+ const { gitUrl, hereyaBinPath, password, targetDir, username } = input;
78
+ // Build the credential helper config
79
+ // The ! prefix tells git to treat it as a shell command
80
+ const credHelper = `!${hereyaBinPath} credential-helper`;
81
+ return new Promise((resolve) => {
82
+ const env = { ...process.env };
83
+ if (username)
84
+ env.hereyaGitUsername = username;
85
+ if (password)
86
+ env.hereyaGitPassword = password;
87
+ const child = spawn('git', [
88
+ 'clone',
89
+ '-c', `credential.helper=${credHelper}`,
90
+ gitUrl,
91
+ targetDir,
92
+ ], {
93
+ env,
94
+ stdio: ['pipe', 'pipe', 'pipe'],
95
+ });
96
+ let stderr = '';
97
+ child.stderr?.on('data', (data) => {
98
+ stderr += data.toString();
99
+ });
100
+ child.on('close', (code) => {
101
+ if (code === 0) {
102
+ resolve({ success: true });
103
+ }
104
+ else {
105
+ resolve({ reason: `git clone failed (exit code ${code}): ${stderr.trim()}`, success: false });
106
+ }
107
+ });
108
+ child.on('error', (err) => {
109
+ resolve({ reason: `git clone failed: ${err.message}`, success: false });
110
+ });
111
+ });
112
+ }
113
+ export async function setupCredentialHelper(input) {
114
+ const { hereyaBinPath, projectDir } = input;
115
+ const credHelper = `!${hereyaBinPath} credential-helper`;
116
+ // Use spawn without shell to avoid quoting issues on Windows
117
+ // (shell: true splits on spaces; single quotes are literal on cmd.exe)
118
+ await new Promise((resolve, reject) => {
119
+ const child = spawn('git', ['config', 'credential.helper', credHelper], {
120
+ cwd: projectDir,
121
+ stdio: 'pipe',
122
+ });
123
+ child.on('close', (code) => {
124
+ if (code === 0)
125
+ resolve();
126
+ else
127
+ reject(new Error(`git config failed with exit code ${code}`));
128
+ });
129
+ child.on('error', reject);
130
+ });
131
+ }
@@ -0,0 +1,6 @@
1
+ export declare const hereyaTokenUtils: {
2
+ generateHereyaToken(description: string): Promise<null | {
3
+ cloudUrl: string;
4
+ token: string;
5
+ }>;
6
+ };
@@ -0,0 +1,29 @@
1
+ import { getCloudCredentials, loadBackendConfig } from '../backend/config.js';
2
+ export const hereyaTokenUtils = {
3
+ async generateHereyaToken(description) {
4
+ const backendConfig = await loadBackendConfig();
5
+ if (!backendConfig.cloud) {
6
+ return null;
7
+ }
8
+ const { clientId, url } = backendConfig.cloud;
9
+ const credentials = await getCloudCredentials(clientId);
10
+ if (!credentials) {
11
+ return null;
12
+ }
13
+ const formData = new FormData();
14
+ formData.append('description', description);
15
+ formData.append('expiresInDays', '365');
16
+ const response = await fetch(`${url}/api/personal-tokens`, {
17
+ body: formData,
18
+ headers: {
19
+ Authorization: `Bearer ${credentials.accessToken}`,
20
+ },
21
+ method: 'POST',
22
+ });
23
+ if (!response.ok) {
24
+ return null;
25
+ }
26
+ const result = await response.json();
27
+ return { cloudUrl: url, token: result.data.token };
28
+ },
29
+ };
@@ -102,6 +102,34 @@
102
102
  "index.js"
103
103
  ]
104
104
  },
105
+ "credential-helper": {
106
+ "aliases": [],
107
+ "args": {
108
+ "operation": {
109
+ "description": "Git credential operation (get, store, erase)",
110
+ "name": "operation",
111
+ "required": true
112
+ }
113
+ },
114
+ "description": "Git credential helper for hereya-managed repositories",
115
+ "flags": {},
116
+ "hasDynamicHelp": false,
117
+ "hidden": true,
118
+ "hiddenAliases": [],
119
+ "id": "credential-helper",
120
+ "pluginAlias": "hereya-cli",
121
+ "pluginName": "hereya-cli",
122
+ "pluginType": "core",
123
+ "strict": true,
124
+ "enableJsonFlag": false,
125
+ "isESM": true,
126
+ "relativePath": [
127
+ "dist",
128
+ "commands",
129
+ "credential-helper",
130
+ "index.js"
131
+ ]
132
+ },
105
133
  "delete-state": {
106
134
  "aliases": [],
107
135
  "args": {},
@@ -459,7 +487,9 @@
459
487
  "description": "Initialize hereya in a project directory.",
460
488
  "examples": [
461
489
  "<%= config.bin %> <%= command.id %> myProject -w=defaultWorkspace",
462
- "<%= config.bin %> <%= command.id %> myProject -w=defaultWorkspace --chdir=./myProject"
490
+ "<%= config.bin %> <%= command.id %> myProject -w=defaultWorkspace --chdir=./myProject",
491
+ "<%= config.bin %> <%= command.id %> myProject -w=dev -t=acme/node-starter",
492
+ "<%= config.bin %> <%= command.id %> myProject -w=dev -t=acme/node-starter -p region=us-east-1"
463
493
  ],
464
494
  "flags": {
465
495
  "chdir": {
@@ -470,6 +500,24 @@
470
500
  "multiple": false,
471
501
  "type": "option"
472
502
  },
503
+ "parameter": {
504
+ "char": "p",
505
+ "description": "template parameter, in the form of 'key=value'. Can be specified multiple times.",
506
+ "name": "parameter",
507
+ "default": [],
508
+ "hasDynamicHelp": false,
509
+ "multiple": true,
510
+ "type": "option"
511
+ },
512
+ "template": {
513
+ "char": "t",
514
+ "description": "template package name (e.g. owner/repo)",
515
+ "name": "template",
516
+ "required": false,
517
+ "hasDynamicHelp": false,
518
+ "multiple": false,
519
+ "type": "option"
520
+ },
473
521
  "workspace": {
474
522
  "char": "w",
475
523
  "description": "workspace to set as default",
@@ -898,6 +946,54 @@
898
946
  "index.js"
899
947
  ]
900
948
  },
949
+ "uninit": {
950
+ "aliases": [],
951
+ "args": {
952
+ "project": {
953
+ "description": "project name",
954
+ "name": "project",
955
+ "required": true
956
+ }
957
+ },
958
+ "description": "Destroy template infrastructure and uninitialize a project.",
959
+ "examples": [
960
+ "<%= config.bin %> <%= command.id %> myProject -w dev"
961
+ ],
962
+ "flags": {
963
+ "chdir": {
964
+ "description": "directory to run command in",
965
+ "name": "chdir",
966
+ "required": false,
967
+ "hasDynamicHelp": false,
968
+ "multiple": false,
969
+ "type": "option"
970
+ },
971
+ "workspace": {
972
+ "char": "w",
973
+ "description": "workspace used during init",
974
+ "name": "workspace",
975
+ "required": true,
976
+ "hasDynamicHelp": false,
977
+ "multiple": false,
978
+ "type": "option"
979
+ }
980
+ },
981
+ "hasDynamicHelp": false,
982
+ "hiddenAliases": [],
983
+ "id": "uninit",
984
+ "pluginAlias": "hereya-cli",
985
+ "pluginName": "hereya-cli",
986
+ "pluginType": "core",
987
+ "strict": true,
988
+ "enableJsonFlag": false,
989
+ "isESM": true,
990
+ "relativePath": [
991
+ "dist",
992
+ "commands",
993
+ "uninit",
994
+ "index.js"
995
+ ]
996
+ },
901
997
  "up": {
902
998
  "aliases": [],
903
999
  "args": {},
@@ -2238,7 +2334,9 @@
2238
2334
  },
2239
2335
  "description": "Initialize a project on a remote dev environment.",
2240
2336
  "examples": [
2241
- "<%= config.bin %> <%= command.id %> my-app -w my-workspace"
2337
+ "<%= config.bin %> <%= command.id %> my-app -w my-workspace",
2338
+ "<%= config.bin %> <%= command.id %> my-app -w my-workspace -t acme/node-starter",
2339
+ "<%= config.bin %> <%= command.id %> my-app -w my-workspace -t acme/node-starter -p region=us-east-1"
2242
2340
  ],
2243
2341
  "flags": {
2244
2342
  "force": {
@@ -2248,6 +2346,25 @@
2248
2346
  "allowNo": false,
2249
2347
  "type": "boolean"
2250
2348
  },
2349
+ "parameter": {
2350
+ "char": "p",
2351
+ "description": "parameter for the template, in the form of 'key=value'. Can be specified multiple times.",
2352
+ "name": "parameter",
2353
+ "required": false,
2354
+ "default": [],
2355
+ "hasDynamicHelp": false,
2356
+ "multiple": true,
2357
+ "type": "option"
2358
+ },
2359
+ "template": {
2360
+ "char": "t",
2361
+ "description": "template package to scaffold the project from",
2362
+ "name": "template",
2363
+ "required": false,
2364
+ "hasDynamicHelp": false,
2365
+ "multiple": false,
2366
+ "type": "option"
2367
+ },
2251
2368
  "workspace": {
2252
2369
  "char": "w",
2253
2370
  "description": "name of the workspace",
@@ -2276,6 +2393,56 @@
2276
2393
  "index.js"
2277
2394
  ]
2278
2395
  },
2396
+ "devenv:project:uninit": {
2397
+ "aliases": [],
2398
+ "args": {
2399
+ "project": {
2400
+ "description": "project name",
2401
+ "name": "project",
2402
+ "required": true
2403
+ }
2404
+ },
2405
+ "description": "Uninitialize a project on a remote dev environment.",
2406
+ "examples": [
2407
+ "<%= config.bin %> <%= command.id %> my-app -w my-workspace",
2408
+ "<%= config.bin %> <%= command.id %> my-app -w my-workspace --force"
2409
+ ],
2410
+ "flags": {
2411
+ "force": {
2412
+ "char": "f",
2413
+ "description": "also remove the project directory after uninit",
2414
+ "name": "force",
2415
+ "allowNo": false,
2416
+ "type": "boolean"
2417
+ },
2418
+ "workspace": {
2419
+ "char": "w",
2420
+ "description": "name of the workspace",
2421
+ "name": "workspace",
2422
+ "required": true,
2423
+ "hasDynamicHelp": false,
2424
+ "multiple": false,
2425
+ "type": "option"
2426
+ }
2427
+ },
2428
+ "hasDynamicHelp": false,
2429
+ "hiddenAliases": [],
2430
+ "id": "devenv:project:uninit",
2431
+ "pluginAlias": "hereya-cli",
2432
+ "pluginName": "hereya-cli",
2433
+ "pluginType": "core",
2434
+ "strict": true,
2435
+ "enableJsonFlag": false,
2436
+ "isESM": true,
2437
+ "relativePath": [
2438
+ "dist",
2439
+ "commands",
2440
+ "devenv",
2441
+ "project",
2442
+ "uninit",
2443
+ "index.js"
2444
+ ]
2445
+ },
2279
2446
  "flow:docker:run": {
2280
2447
  "aliases": [],
2281
2448
  "args": {
@@ -2578,5 +2745,5 @@
2578
2745
  ]
2579
2746
  }
2580
2747
  },
2581
- "version": "0.72.1"
2748
+ "version": "0.73.0"
2582
2749
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "hereya-cli",
3
3
  "description": "Infrastructure as Package",
4
- "version": "0.72.1",
4
+ "version": "0.73.0",
5
5
  "author": "Hereya Developers",
6
6
  "bin": {
7
7
  "hereya": "./bin/run.js"