hereya-cli 0.55.0 → 0.57.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.
Files changed (35) hide show
  1. package/README.md +94 -36
  2. package/dist/backend/cloud/cloud-backend.d.ts +5 -1
  3. package/dist/backend/cloud/cloud-backend.js +208 -0
  4. package/dist/backend/common.d.ts +64 -2
  5. package/dist/backend/file.d.ts +5 -1
  6. package/dist/backend/file.js +24 -0
  7. package/dist/commands/add/index.js +2 -1
  8. package/dist/commands/deploy/index.js +50 -24
  9. package/dist/commands/doc/index.d.ts +17 -0
  10. package/dist/commands/doc/index.js +154 -0
  11. package/dist/commands/down/index.js +40 -11
  12. package/dist/commands/import/index.js +2 -1
  13. package/dist/commands/publish/index.d.ts +32 -0
  14. package/dist/commands/publish/index.js +239 -0
  15. package/dist/commands/remove/index.js +16 -4
  16. package/dist/commands/undeploy/index.js +45 -17
  17. package/dist/commands/up/index.js +36 -11
  18. package/dist/commands/workspace/install/index.js +1 -1
  19. package/dist/commands/workspace/uninstall/index.js +18 -5
  20. package/dist/executor/interface.d.ts +5 -0
  21. package/dist/executor/local.js +3 -3
  22. package/dist/infrastructure/index.d.ts +2 -0
  23. package/dist/infrastructure/index.js +8 -8
  24. package/dist/lib/config/common.d.ts +1 -0
  25. package/dist/lib/config/simple.js +2 -2
  26. package/dist/lib/package/cloud.d.ts +19 -0
  27. package/dist/lib/package/cloud.js +226 -0
  28. package/dist/lib/package/common.d.ts +1 -0
  29. package/dist/lib/package/github.d.ts +1 -1
  30. package/dist/lib/package/github.js +33 -6
  31. package/dist/lib/package/index.d.ts +9 -4
  32. package/dist/lib/package/index.js +178 -35
  33. package/dist/lib/package/local.js +1 -0
  34. package/oclif.manifest.json +92 -1
  35. package/package.json +8 -1
@@ -1,42 +1,139 @@
1
1
  import * as yaml from 'yaml';
2
2
  import { z } from 'zod';
3
+ import { getCurrentBackendType } from '../../backend/config.js';
4
+ import { BackendType, getBackend } from '../../backend/index.js';
3
5
  import { IacType } from '../../iac/common.js';
4
6
  import { InfrastructureType } from '../../infrastructure/common.js';
7
+ import { CloudPackageManager } from './cloud.js';
5
8
  import { GitHubPackageManager } from './github.js';
6
9
  import { LocalPackageManager } from './local.js';
7
- export const packageManager = new GitHubPackageManager();
10
+ export const githubPackageManager = new GitHubPackageManager();
8
11
  export const localPackageManager = new LocalPackageManager();
9
- export function getPackageManager(protocol) {
12
+ export async function getPackageManager(protocol, logger) {
10
13
  if (protocol === 'local') {
11
14
  return localPackageManager;
12
15
  }
13
- return packageManager;
16
+ if (protocol === 'cloud') {
17
+ const backend = await getBackend();
18
+ return new CloudPackageManager(backend, logger);
19
+ }
20
+ // Default to GitHub
21
+ return githubPackageManager;
14
22
  }
15
23
  export async function resolvePackage(input) {
16
- if (input.package.includes('.')) {
24
+ // Parse package spec to extract name and version
25
+ const { packageName, version } = parsePackageSpec(input.package);
26
+ // Validate package name format
27
+ if (packageName.includes('.')) {
17
28
  return { found: false, reason: 'Invalid package format. Package name cannot contain dots (.) nor double dashes (--)' };
18
29
  }
19
- if (input.package.includes('--')) {
30
+ if (packageName.includes('--')) {
20
31
  return { found: false, reason: 'Invalid package format. Package name cannot contain dots (.) nor double dashes (--)' };
21
32
  }
22
- const isLocal = input.package.startsWith('local/');
33
+ const isLocal = packageName.startsWith('local/');
34
+ // Try cloud first if not local and backend is cloud
35
+ if (!isLocal) {
36
+ const backendType = await getCurrentBackendType();
37
+ if (backendType === BackendType.Cloud) {
38
+ const result = await tryCloudResolution(input);
39
+ if (result.found) {
40
+ return result; // Success
41
+ }
42
+ // Failed - check if we should fallback to GitHub or return the error
43
+ if (!packageName.includes('/')) {
44
+ // Simple package names can't fallback to GitHub, return the detailed error
45
+ return result;
46
+ }
47
+ // For owner/repo format, we can fallback to GitHub
48
+ input.logger?.debug(`Package ${packageName} not found in registry, trying GitHub...`);
49
+ input.logger?.debug(`Registry error: ${result.reason}`);
50
+ }
51
+ }
52
+ // Determine protocol for standard resolution
53
+ const protocol = isLocal ? 'local' : '';
54
+ // Standard resolution (GitHub or local)
55
+ return resolveWithStandardManager(protocol, input, packageName, version);
56
+ }
57
+ export function getPackageCanonicalName(packageName) {
58
+ return packageName.replaceAll('/', '--');
59
+ }
60
+ export async function downloadPackage(pkgUrl, destPath) {
61
+ // Determine protocol based on URL characteristics
62
+ let protocol = '';
63
+ if (pkgUrl.startsWith('local/')) {
64
+ protocol = 'local';
65
+ }
66
+ else if (pkgUrl.includes('#')) {
67
+ // Registry package URLs contain commit SHA
68
+ const backendType = await getCurrentBackendType();
69
+ if (backendType === BackendType.Cloud) {
70
+ protocol = 'cloud';
71
+ }
72
+ }
73
+ // Otherwise defaults to GitHub
74
+ const packageManager = await getPackageManager(protocol);
75
+ return packageManager.downloadPackage(pkgUrl, destPath);
76
+ }
77
+ export const PackageMetadata = z.object({
78
+ dependencies: z.record(z.string()).optional(),
79
+ deploy: z.boolean().optional(),
80
+ iac: z.nativeEnum(IacType),
81
+ infra: z.nativeEnum(InfrastructureType),
82
+ onDeploy: z
83
+ .object({
84
+ pkg: z.string(),
85
+ version: z.string(),
86
+ })
87
+ .optional(),
88
+ originalInfra: z.nativeEnum(InfrastructureType).optional(),
89
+ });
90
+ // Helper function to parse package spec into name and version
91
+ function parsePackageSpec(spec) {
92
+ const match = spec.match(/^([^@]+)(?:@(.+))?$/);
93
+ if (!match) {
94
+ return { packageName: spec };
95
+ }
96
+ const [, packageName, version] = match;
97
+ return { packageName, version };
98
+ }
99
+ // Helper function to try cloud resolution
100
+ async function tryCloudResolution(input) {
101
+ const cloudManager = await getPackageManager('cloud', input.logger);
102
+ const cloudPackageInfo = await cloudManager.resolvePackage(input.package);
103
+ if (cloudPackageInfo) {
104
+ // Successfully found in registry, now get metadata
105
+ input.logger?.debug(`Found package in registry: ${cloudPackageInfo.name}@${cloudPackageInfo.version}`);
106
+ const result = await resolveCloudPackage(cloudPackageInfo, cloudManager, input);
107
+ input.logger?.debug(`Metadata resolution result: ${result.found ? 'success' : result.reason}`);
108
+ return result; // Return success or failure with detailed reason
109
+ }
110
+ // Package not found in registry at all
111
+ return {
112
+ found: false,
113
+ reason: `Package '${input.package}' not found in cloud registry. Make sure the package name is correct and you have access to it.`
114
+ };
115
+ }
116
+ // Helper function for standard (GitHub/local) resolution
117
+ async function resolveWithStandardManager(protocol, input, packageName, version) {
118
+ // Standard managers don't support logger yet
119
+ // Parse owner/repo from package name
23
120
  let [owner, repo] = ['', ''];
24
- if (isLocal) {
121
+ if (protocol === 'local') {
25
122
  owner = 'local';
26
- repo = input.package.replace('local/', '');
123
+ repo = packageName.replace('local/', '');
27
124
  }
28
125
  else {
29
- const pkgParts = input.package.split('/');
126
+ const pkgParts = packageName.split('/');
30
127
  if (pkgParts.length !== 2) {
31
- return { found: false, reason: 'Invalid package format. Use owner/repository' };
128
+ return { found: false, reason: 'Invalid package format. Use owner/repository or org/name' };
32
129
  }
33
130
  ;
34
131
  [owner, repo] = pkgParts;
35
132
  }
36
- const packageManager = getPackageManager(isLocal ? 'local' : '');
133
+ const packageManager = await getPackageManager(protocol);
37
134
  const metadataContentCandidates = (await Promise.all([
38
- packageManager.getRepoContent({ owner, path: 'hereyarc.yaml', projectRootDir: input.projectRootDir, repo }),
39
- packageManager.getRepoContent({ owner, path: 'hereyarc.yml', projectRootDir: input.projectRootDir, repo }),
135
+ packageManager.getRepoContent({ owner, path: 'hereyarc.yaml', projectRootDir: input.projectRootDir, repo, version }),
136
+ packageManager.getRepoContent({ owner, path: 'hereyarc.yml', projectRootDir: input.projectRootDir, repo, version }),
40
137
  ])).filter((content$) => content$.found);
41
138
  if (metadataContentCandidates.length === 0) {
42
139
  return { found: false, reason: `No hereya metadata file found in ${input.package}` };
@@ -48,37 +145,83 @@ export async function resolvePackage(input) {
48
145
  return { found: false, reason: 'Package has dependencies but is not a deploy package' };
49
146
  }
50
147
  if (input.isDeploying && metadata.onDeploy) {
51
- return resolvePackage({ package: metadata.onDeploy.pkg, projectRootDir: input.projectRootDir });
148
+ return resolvePackage({ ...input, package: metadata.onDeploy.pkg });
52
149
  }
53
150
  return {
54
- canonicalName: getPackageCanonicalName(input.package),
151
+ canonicalName: getPackageCanonicalName(packageName),
55
152
  found: true,
56
153
  metadata,
57
154
  packageUri: metadataContent$.pkgUrl,
58
- pkgName: input.package,
155
+ pkgName: packageName,
156
+ version: version || '',
59
157
  };
60
158
  }
61
159
  catch (error) {
62
160
  return { found: false, reason: error.message };
63
161
  }
64
162
  }
65
- export function getPackageCanonicalName(packageName) {
66
- return packageName.replaceAll('/', '--');
67
- }
68
- export async function downloadPackage(pkgUrl, destPath) {
69
- const packageManager = getPackageManager(pkgUrl.startsWith('local/') ? 'local' : '');
70
- return packageManager.downloadPackage(pkgUrl, destPath);
163
+ // Helper function to resolve cloud package metadata
164
+ async function resolveCloudPackage(packageInfo, cloudManager, input) {
165
+ // Parse package name to determine owner/repo for getRepoContent interface
166
+ // For simple names like 'mongo', owner will be empty
167
+ // For org/name format like 'hereya/mongo', split into owner and repo
168
+ const parts = packageInfo.name.split('/');
169
+ const owner = parts.length === 2 ? parts[0] : '';
170
+ const repo = parts.length === 2 ? parts[1] : packageInfo.name;
171
+ // Get metadata using the standard getRepoContent interface
172
+ // CloudPackageManager will reconstruct the package name and look it up in registry
173
+ const metadataContentCandidates = (await Promise.all([
174
+ cloudManager.getRepoContent({
175
+ owner,
176
+ path: 'hereyarc.yaml',
177
+ projectRootDir: input.projectRootDir,
178
+ repo,
179
+ version: packageInfo.version,
180
+ }),
181
+ cloudManager.getRepoContent({
182
+ owner,
183
+ path: 'hereyarc.yml',
184
+ projectRootDir: input.projectRootDir,
185
+ repo,
186
+ version: packageInfo.version,
187
+ }),
188
+ ])).filter((content$) => content$.found);
189
+ if (metadataContentCandidates.length === 0) {
190
+ // Try to get more detailed error from the last attempt
191
+ const lastAttempt = await cloudManager.getRepoContent({
192
+ owner,
193
+ path: 'hereyarc.yaml',
194
+ projectRootDir: input.projectRootDir,
195
+ repo,
196
+ });
197
+ const detailedReason = !lastAttempt.found && lastAttempt.reason
198
+ ? lastAttempt.reason
199
+ : `No hereya metadata file (hereyarc.yaml or hereyarc.yml) found for package '${packageInfo.name}'`;
200
+ return { found: false, reason: detailedReason };
201
+ }
202
+ const metadataContent$ = metadataContentCandidates[0];
203
+ try {
204
+ const metadata = PackageMetadata.parse(yaml.parse(metadataContent$.content));
205
+ if (!metadata.deploy && metadata.dependencies) {
206
+ return { found: false, reason: 'Package has dependencies but is not a deploy package' };
207
+ }
208
+ if (input.isDeploying && metadata.onDeploy) {
209
+ return resolvePackage({ ...input, package: metadata.onDeploy.pkg });
210
+ }
211
+ // Build packageUri with repository and commit
212
+ const packageUri = packageInfo.registryPackage.commit
213
+ ? `${packageInfo.registryPackage.repository}#${packageInfo.registryPackage.commit}`
214
+ : packageInfo.registryPackage.repository || metadataContent$.pkgUrl;
215
+ return {
216
+ canonicalName: getPackageCanonicalName(packageInfo.name),
217
+ found: true,
218
+ metadata,
219
+ packageUri,
220
+ pkgName: packageInfo.name,
221
+ version: packageInfo.version,
222
+ };
223
+ }
224
+ catch (error) {
225
+ return { found: false, reason: error.message };
226
+ }
71
227
  }
72
- export const PackageMetadata = z.object({
73
- dependencies: z.record(z.string()).optional(),
74
- deploy: z.boolean().optional(),
75
- iac: z.nativeEnum(IacType),
76
- infra: z.nativeEnum(InfrastructureType),
77
- onDeploy: z
78
- .object({
79
- pkg: z.string(),
80
- version: z.string(),
81
- })
82
- .optional(),
83
- originalInfra: z.nativeEnum(InfrastructureType).optional(),
84
- });
@@ -7,6 +7,7 @@ export class LocalPackageManager {
7
7
  return destPath;
8
8
  }
9
9
  async getRepoContent({ path: filePath, projectRootDir, repo }) {
10
+ // Local packages don't have versions, version parameter is ignored
10
11
  try {
11
12
  const pkgRootDir = path.join(projectRootDir ?? process.cwd(), repo);
12
13
  const resolvedPath = path.join(pkgRootDir, filePath);
@@ -193,6 +193,63 @@
193
193
  "index.js"
194
194
  ]
195
195
  },
196
+ "doc": {
197
+ "aliases": [],
198
+ "args": {
199
+ "package": {
200
+ "description": "Package name with optional version (e.g., my-package or my-package@1.0.0)",
201
+ "name": "package",
202
+ "required": true
203
+ }
204
+ },
205
+ "description": "Display documentation for a package from the registry",
206
+ "examples": [
207
+ "<%= config.bin %> <%= command.id %> my-package",
208
+ "<%= config.bin %> <%= command.id %> my-package@1.0.0",
209
+ "<%= config.bin %> <%= command.id %> my-package --json",
210
+ "<%= config.bin %> <%= command.id %> my-package --no-doc"
211
+ ],
212
+ "flags": {
213
+ "chdir": {
214
+ "char": "C",
215
+ "description": "directory to run command in",
216
+ "helpGroup": "global",
217
+ "name": "chdir",
218
+ "default": ".",
219
+ "hasDynamicHelp": false,
220
+ "multiple": false,
221
+ "type": "option"
222
+ },
223
+ "json": {
224
+ "char": "j",
225
+ "description": "Output in JSON format",
226
+ "name": "json",
227
+ "allowNo": false,
228
+ "type": "boolean"
229
+ },
230
+ "no-doc": {
231
+ "description": "Show only metadata without the full documentation",
232
+ "name": "no-doc",
233
+ "allowNo": false,
234
+ "type": "boolean"
235
+ }
236
+ },
237
+ "hasDynamicHelp": false,
238
+ "hiddenAliases": [],
239
+ "id": "doc",
240
+ "pluginAlias": "hereya-cli",
241
+ "pluginName": "hereya-cli",
242
+ "pluginType": "core",
243
+ "strict": true,
244
+ "enableJsonFlag": false,
245
+ "isESM": true,
246
+ "relativePath": [
247
+ "dist",
248
+ "commands",
249
+ "doc",
250
+ "index.js"
251
+ ]
252
+ },
196
253
  "down": {
197
254
  "aliases": [],
198
255
  "args": {},
@@ -507,6 +564,40 @@
507
564
  "index.js"
508
565
  ]
509
566
  },
567
+ "publish": {
568
+ "aliases": [],
569
+ "args": {},
570
+ "description": "Publish a package to the Hereya registry",
571
+ "examples": [
572
+ "$ hereya publish",
573
+ "$ hereya publish --chdir=/path/to/package"
574
+ ],
575
+ "flags": {
576
+ "chdir": {
577
+ "description": "\n Directory where the command will be executed.\n If not specified, it defaults to the current working directory.\n ",
578
+ "name": "chdir",
579
+ "required": false,
580
+ "hasDynamicHelp": false,
581
+ "multiple": false,
582
+ "type": "option"
583
+ }
584
+ },
585
+ "hasDynamicHelp": false,
586
+ "hiddenAliases": [],
587
+ "id": "publish",
588
+ "pluginAlias": "hereya-cli",
589
+ "pluginName": "hereya-cli",
590
+ "pluginType": "core",
591
+ "strict": true,
592
+ "enableJsonFlag": false,
593
+ "isESM": true,
594
+ "relativePath": [
595
+ "dist",
596
+ "commands",
597
+ "publish",
598
+ "index.js"
599
+ ]
600
+ },
510
601
  "remove": {
511
602
  "aliases": [],
512
603
  "args": {
@@ -1786,5 +1877,5 @@
1786
1877
  ]
1787
1878
  }
1788
1879
  },
1789
- "version": "0.55.0"
1880
+ "version": "0.57.0"
1790
1881
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "hereya-cli",
3
3
  "description": "Infrastructure as Package",
4
- "version": "0.55.0",
4
+ "version": "0.57.0",
5
5
  "author": "Hereya Developers",
6
6
  "bin": {
7
7
  "hereya": "./bin/run.js"
@@ -19,10 +19,13 @@
19
19
  "@oclif/core": "^4.2.6",
20
20
  "@oclif/plugin-help": "^6.2.25",
21
21
  "@oclif/plugin-plugins": "^5.4.31",
22
+ "chalk": "^5.5.0",
22
23
  "glob": "^11.0.1",
23
24
  "ignore": "^7.0.3",
24
25
  "keytar": "^7.9.0",
25
26
  "listr2": "^8.2.5",
27
+ "marked": "^16.1.2",
28
+ "marked-terminal": "7.3.0",
26
29
  "node-machine-id": "^1.1.12",
27
30
  "open": "^10.1.1",
28
31
  "simple-git": "^3.27.0",
@@ -35,10 +38,13 @@
35
38
  "@oclif/test": "^4.1.9",
36
39
  "@types/chai": "^5.0.1",
37
40
  "@types/jsonwebtoken": "^9.0.9",
41
+ "@types/marked-terminal": "^6.1.1",
38
42
  "@types/mocha": "^10.0.10",
39
43
  "@types/mock-fs": "^4.13.4",
40
44
  "@types/node": "^22",
41
45
  "@types/sinon": "^17.0.3",
46
+ "@types/sinon-chai": "^4.0.0",
47
+ "@types/tar": "^6.1.13",
42
48
  "@types/unzip-stream": "^0.3.4",
43
49
  "chai": "^5.1.2",
44
50
  "eslint": "^9.20.1",
@@ -52,6 +58,7 @@
52
58
  "oclif": "^4.17.27",
53
59
  "shx": "^0.3.4",
54
60
  "sinon": "^19.0.2",
61
+ "sinon-chai": "^4.0.0",
55
62
  "tsx": "^4.20.3",
56
63
  "typescript": "^5.7.3"
57
64
  },