@hyperdrive.bot/cli 1.0.13 → 1.0.16
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.
- package/README.md +1495 -474
- package/dist/commands/deploy.d.ts +18 -0
- package/dist/commands/deploy.js +239 -0
- package/dist/commands/deployment/create.js +10 -2
- package/dist/commands/domain/{switch.d.ts → set-production.d.ts} +1 -1
- package/dist/commands/domain/set-production.js +27 -0
- package/dist/commands/git/list-open-prs.d.ts +12 -0
- package/dist/commands/git/list-open-prs.js +87 -0
- package/dist/commands/hook/add.d.ts +22 -0
- package/dist/commands/hook/add.js +299 -0
- package/dist/commands/hook/list.d.ts +11 -0
- package/dist/commands/hook/list.js +111 -0
- package/dist/commands/hook/logs.d.ts +13 -0
- package/dist/commands/hook/logs.js +124 -0
- package/dist/commands/hook/remove.d.ts +12 -0
- package/dist/commands/hook/remove.js +115 -0
- package/dist/commands/hook/toggle.d.ts +12 -0
- package/dist/commands/hook/toggle.js +125 -0
- package/dist/commands/init.d.ts +1 -1
- package/dist/commands/init.js +49 -9
- package/dist/commands/module/bindings.d.ts +14 -0
- package/dist/commands/module/bindings.js +125 -0
- package/dist/commands/module/create.d.ts +3 -0
- package/dist/commands/module/create.js +156 -78
- package/dist/commands/module/list.d.ts +1 -0
- package/dist/commands/module/list.js +22 -1
- package/dist/commands/module/sync.d.ts +29 -0
- package/dist/commands/module/sync.js +409 -0
- package/dist/commands/module/unlink.d.ts +11 -0
- package/dist/commands/module/unlink.js +77 -0
- package/dist/commands/module/update.d.ts +10 -0
- package/dist/commands/module/update.js +168 -5
- package/dist/commands/network/discover.d.ts +12 -0
- package/dist/commands/network/discover.js +210 -0
- package/dist/commands/network/get.d.ts +13 -0
- package/dist/commands/network/get.js +90 -0
- package/dist/commands/{auth/logout.d.ts → network/list.d.ts} +2 -9
- package/dist/commands/network/list.js +71 -0
- package/dist/commands/network/register.d.ts +16 -0
- package/dist/commands/network/register.js +144 -0
- package/dist/commands/parameter/sync.d.ts +13 -0
- package/dist/commands/parameter/sync.js +69 -1
- package/dist/commands/project/sync.d.ts +5 -11
- package/dist/commands/project/sync.js +12 -381
- package/dist/commands/seed.d.ts +93 -0
- package/dist/commands/seed.js +324 -0
- package/dist/commands/service/backup.d.ts +17 -0
- package/dist/commands/service/backup.js +156 -0
- package/dist/commands/service/backups.d.ts +14 -0
- package/dist/commands/service/backups.js +110 -0
- package/dist/commands/service/bind.d.ts +16 -0
- package/dist/commands/service/bind.js +106 -0
- package/dist/commands/service/bindings.d.ts +13 -0
- package/dist/commands/service/bindings.js +78 -0
- package/dist/commands/service/clone.d.ts +19 -0
- package/dist/commands/service/clone.js +153 -0
- package/dist/commands/service/create.d.ts +16 -0
- package/dist/commands/service/create.js +212 -0
- package/dist/commands/service/get.d.ts +13 -0
- package/dist/commands/service/get.js +97 -0
- package/dist/commands/service/list.d.ts +12 -0
- package/dist/commands/service/list.js +86 -0
- package/dist/commands/service/register.d.ts +21 -0
- package/dist/commands/service/register.js +215 -0
- package/dist/commands/service/restore.d.ts +19 -0
- package/dist/commands/service/restore.js +158 -0
- package/dist/commands/service/seed.d.ts +17 -0
- package/dist/commands/service/seed.js +173 -0
- package/dist/commands/service/templates.d.ts +10 -0
- package/dist/commands/service/templates.js +66 -0
- package/dist/commands/service/unbind.d.ts +15 -0
- package/dist/commands/service/unbind.js +74 -0
- package/dist/commands/stage/create.d.ts +23 -0
- package/dist/commands/stage/create.js +145 -6
- package/dist/commands/stage/delete.d.ts +11 -0
- package/dist/commands/stage/delete.js +85 -0
- package/dist/commands/stage/deploy.d.ts +34 -0
- package/dist/commands/stage/deploy.js +294 -0
- package/dist/commands/stage/ensure-branches.d.ts +23 -0
- package/dist/commands/stage/ensure-branches.js +101 -0
- package/dist/commands/stage/list.js +4 -0
- package/dist/commands/stage/status.d.ts +14 -0
- package/dist/commands/stage/status.js +100 -0
- package/dist/commands/{jira → tracker}/connect.js +32 -23
- package/dist/commands/tracker/hook/add.d.ts +25 -0
- package/dist/commands/tracker/hook/add.js +284 -0
- package/dist/commands/{jira → tracker}/hook/list.js +20 -11
- package/dist/commands/{jira/hook/add.d.ts → tracker/hook/logs.d.ts} +2 -3
- package/dist/commands/tracker/hook/logs.js +126 -0
- package/dist/commands/{jira → tracker}/hook/remove.js +9 -8
- package/dist/commands/{jira → tracker}/hook/toggle.js +14 -12
- package/dist/commands/tracker/project/init.d.ts +17 -0
- package/dist/commands/tracker/project/init.js +178 -0
- package/dist/commands/tracker/project/link-module.d.ts +17 -0
- package/dist/commands/tracker/project/link-module.js +287 -0
- package/dist/commands/tracker/project/list-modules.d.ts +11 -0
- package/dist/commands/tracker/project/list-modules.js +117 -0
- package/dist/commands/tracker/project/list.d.ts +10 -0
- package/dist/commands/tracker/project/list.js +90 -0
- package/dist/commands/tracker/project/status.d.ts +13 -0
- package/dist/commands/tracker/project/status.js +168 -0
- package/dist/commands/tracker/project/unlink-module.d.ts +13 -0
- package/dist/commands/tracker/project/unlink-module.js +251 -0
- package/dist/commands/{jira → tracker}/status.js +3 -3
- package/dist/lib/ensure-branches.d.ts +53 -0
- package/dist/lib/ensure-branches.js +149 -0
- package/dist/lib/git-providers/github.d.ts +16 -0
- package/dist/lib/git-providers/github.js +157 -0
- package/dist/lib/git-providers/gitlab.d.ts +16 -0
- package/dist/lib/git-providers/gitlab.js +148 -0
- package/dist/lib/git-providers/index.d.ts +67 -0
- package/dist/lib/git-providers/index.js +39 -0
- package/dist/lib/lambda-warmer.d.ts +106 -0
- package/dist/lib/lambda-warmer.js +189 -0
- package/dist/services/hyperdrive-sigv4.d.ts +359 -5
- package/dist/services/hyperdrive-sigv4.js +177 -12
- package/dist/utils/hook-flow.d.ts +60 -3
- package/dist/utils/hook-flow.js +437 -2
- package/dist/utils/hook-normalize.d.ts +6 -0
- package/dist/utils/hook-normalize.js +33 -0
- package/dist/utils/lifecycle-poller.d.ts +32 -0
- package/dist/utils/lifecycle-poller.js +72 -0
- package/dist/utils/retry.d.ts +43 -0
- package/dist/utils/retry.js +88 -0
- package/dist/utils/summary-display.js +1 -1
- package/dist/utils/tracker-project-flow.d.ts +84 -0
- package/dist/utils/tracker-project-flow.js +564 -0
- package/package.json +35 -7
- package/dist/commands/auth/login.d.ts +0 -16
- package/dist/commands/auth/login.js +0 -179
- package/dist/commands/auth/logout.js +0 -116
- package/dist/commands/auth/refresh.d.ts +0 -6
- package/dist/commands/auth/refresh.js +0 -66
- package/dist/commands/auth/status.d.ts +0 -6
- package/dist/commands/auth/status.js +0 -63
- package/dist/commands/config/get.d.ts +0 -9
- package/dist/commands/config/get.js +0 -37
- package/dist/commands/config/set.d.ts +0 -10
- package/dist/commands/config/set.js +0 -48
- package/dist/commands/config/show.d.ts +0 -6
- package/dist/commands/config/show.js +0 -10
- package/dist/commands/domain/current.d.ts +0 -6
- package/dist/commands/domain/current.js +0 -18
- package/dist/commands/domain/list.d.ts +0 -6
- package/dist/commands/domain/list.js +0 -42
- package/dist/commands/domain/switch.js +0 -40
- package/dist/commands/jira/hook/add.js +0 -147
- package/dist/services/tenant-service.d.ts +0 -127
- package/dist/services/tenant-service.js +0 -396
- package/dist/utils/auth-flow.d.ts +0 -147
- package/dist/utils/auth-flow.js +0 -479
- package/oclif.manifest.json +0 -3519
- /package/dist/commands/{jira → tracker}/connect.d.ts +0 -0
- /package/dist/commands/{jira → tracker}/hook/list.d.ts +0 -0
- /package/dist/commands/{jira → tracker}/hook/remove.d.ts +0 -0
- /package/dist/commands/{jira → tracker}/hook/toggle.d.ts +0 -0
- /package/dist/commands/{jira → tracker}/status.d.ts +0 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class Deploy extends Command {
|
|
3
|
+
static args: {
|
|
4
|
+
directory: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
5
|
+
};
|
|
6
|
+
static description: string;
|
|
7
|
+
static examples: string[];
|
|
8
|
+
static flags: {
|
|
9
|
+
commit: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
domain: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
moduleSlug: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
name: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
spa: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
14
|
+
stage: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
|
+
type: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
16
|
+
};
|
|
17
|
+
run(): Promise<void>;
|
|
18
|
+
}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
|
4
|
+
import { join, relative } from 'node:path';
|
|
5
|
+
import moment from 'moment';
|
|
6
|
+
import { lookup } from 'mime-types';
|
|
7
|
+
import ora from 'ora';
|
|
8
|
+
import { HyperdriveSigV4Service } from '../services/hyperdrive-sigv4.js';
|
|
9
|
+
import { retryWithBackoff } from '../utils/retry.js';
|
|
10
|
+
// Validation limits — must match server-side constants in static-deployments.ts
|
|
11
|
+
const MAX_FILE_COUNT = 10_000;
|
|
12
|
+
const MAX_TOTAL_SIZE_BYTES = 500 * 1024 * 1024; // 500 MB
|
|
13
|
+
const MAX_SINGLE_FILE_BYTES = 50 * 1024 * 1024; // 50 MB
|
|
14
|
+
/**
|
|
15
|
+
* Recursively walk a directory and return all file paths.
|
|
16
|
+
* Skips hidden files/directories (starting with .)
|
|
17
|
+
* Rejects paths containing '..' (path traversal)
|
|
18
|
+
*/
|
|
19
|
+
function walkDir(dir, baseDir) {
|
|
20
|
+
const results = [];
|
|
21
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
22
|
+
for (const entry of entries) {
|
|
23
|
+
// Skip hidden files and directories
|
|
24
|
+
if (entry.name.startsWith('.'))
|
|
25
|
+
continue;
|
|
26
|
+
const fullPath = join(dir, entry.name);
|
|
27
|
+
if (entry.isDirectory()) {
|
|
28
|
+
results.push(...walkDir(fullPath, baseDir));
|
|
29
|
+
}
|
|
30
|
+
else if (entry.isFile()) {
|
|
31
|
+
const relativePath = relative(baseDir, fullPath);
|
|
32
|
+
// Reject path traversal
|
|
33
|
+
if (relativePath.includes('..'))
|
|
34
|
+
continue;
|
|
35
|
+
results.push(fullPath);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return results;
|
|
39
|
+
}
|
|
40
|
+
function formatBytes(bytes) {
|
|
41
|
+
if (bytes < 1024)
|
|
42
|
+
return `${bytes} B`;
|
|
43
|
+
if (bytes < 1024 * 1024)
|
|
44
|
+
return `${(bytes / 1024).toFixed(0)} KB`;
|
|
45
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
46
|
+
}
|
|
47
|
+
export default class Deploy extends Command {
|
|
48
|
+
static args = {
|
|
49
|
+
directory: Args.string({
|
|
50
|
+
description: 'Path to the directory to deploy',
|
|
51
|
+
required: true,
|
|
52
|
+
}),
|
|
53
|
+
};
|
|
54
|
+
static description = 'Deploy a static site to Hyperdrive (S3 + CloudFront)';
|
|
55
|
+
static examples = [
|
|
56
|
+
'<%= config.bin %> <%= command.id %> ./my-site --type static --stage dev --moduleSlug my-demo',
|
|
57
|
+
'<%= config.bin %> <%= command.id %> ./dist --type static --stage dev -m my-app --name "Release v1.0"',
|
|
58
|
+
];
|
|
59
|
+
static flags = {
|
|
60
|
+
commit: Flags.string({
|
|
61
|
+
char: 'c',
|
|
62
|
+
default: 'static',
|
|
63
|
+
description: 'Commit reference (default: "static")',
|
|
64
|
+
}),
|
|
65
|
+
domain: Flags.string({
|
|
66
|
+
char: 'd',
|
|
67
|
+
description: 'Tenant domain (for multi-domain setups)',
|
|
68
|
+
}),
|
|
69
|
+
moduleSlug: Flags.string({
|
|
70
|
+
char: 'm',
|
|
71
|
+
default() {
|
|
72
|
+
if (existsSync('.hyperdrive.json')) {
|
|
73
|
+
const hyperdriveConfig = JSON.parse(readFileSync('.hyperdrive.json', 'utf8'));
|
|
74
|
+
return hyperdriveConfig.slug;
|
|
75
|
+
}
|
|
76
|
+
return '';
|
|
77
|
+
},
|
|
78
|
+
description: 'Module slug',
|
|
79
|
+
required: true,
|
|
80
|
+
}),
|
|
81
|
+
name: Flags.string({
|
|
82
|
+
char: 'n',
|
|
83
|
+
default: `Deployment-${moment().format('YYYY-MM-DD-HH-mm-ss')}`,
|
|
84
|
+
description: 'Deployment name (auto-generated if omitted)',
|
|
85
|
+
}),
|
|
86
|
+
spa: Flags.boolean({
|
|
87
|
+
default: false,
|
|
88
|
+
description: 'Enable SPA routing mode — returns index.html for 404 paths',
|
|
89
|
+
}),
|
|
90
|
+
stage: Flags.string({
|
|
91
|
+
char: 's',
|
|
92
|
+
description: 'Target stage',
|
|
93
|
+
required: true,
|
|
94
|
+
}),
|
|
95
|
+
type: Flags.string({
|
|
96
|
+
char: 't',
|
|
97
|
+
default: 'static',
|
|
98
|
+
description: 'Deployment type',
|
|
99
|
+
options: ['static'],
|
|
100
|
+
}),
|
|
101
|
+
};
|
|
102
|
+
async run() {
|
|
103
|
+
const { args, flags } = await this.parse(Deploy);
|
|
104
|
+
const dir = args.directory;
|
|
105
|
+
// 1. Validate directory exists
|
|
106
|
+
if (!existsSync(dir)) {
|
|
107
|
+
this.error(`Directory not found: ${dir}`);
|
|
108
|
+
}
|
|
109
|
+
// 2. Validate index.html exists at root
|
|
110
|
+
if (!existsSync(join(dir, 'index.html'))) {
|
|
111
|
+
this.error(`No index.html found in ${dir}`);
|
|
112
|
+
}
|
|
113
|
+
// 3. Walk directory and build file manifest
|
|
114
|
+
const filePaths = walkDir(dir, dir);
|
|
115
|
+
const files = filePaths.map((f) => {
|
|
116
|
+
const relativePath = relative(dir, f);
|
|
117
|
+
const size = statSync(f).size;
|
|
118
|
+
const contentType = lookup(relativePath) || 'application/octet-stream';
|
|
119
|
+
return { contentType, localPath: f, path: relativePath, size };
|
|
120
|
+
});
|
|
121
|
+
const totalSize = files.reduce((sum, f) => sum + f.size, 0);
|
|
122
|
+
this.log(chalk.blue(`\n📦 Static deployment: ${flags.moduleSlug} → ${flags.stage}`));
|
|
123
|
+
this.log(chalk.gray(` Found ${files.length} files (${formatBytes(totalSize)})`));
|
|
124
|
+
this.log('');
|
|
125
|
+
// Pre-flight validation — catch limit violations before making the API call
|
|
126
|
+
if (files.length > MAX_FILE_COUNT) {
|
|
127
|
+
this.error(`File count ${files.length} exceeds maximum of ${MAX_FILE_COUNT}`);
|
|
128
|
+
}
|
|
129
|
+
if (totalSize > MAX_TOTAL_SIZE_BYTES) {
|
|
130
|
+
this.error(`Total size ${formatBytes(totalSize)} exceeds maximum of ${formatBytes(MAX_TOTAL_SIZE_BYTES)}`);
|
|
131
|
+
}
|
|
132
|
+
const oversizedFiles = files.filter(f => f.size > MAX_SINGLE_FILE_BYTES);
|
|
133
|
+
if (oversizedFiles.length > 0) {
|
|
134
|
+
const offenders = oversizedFiles.map(f => `${f.path} (${formatBytes(f.size)})`).join(', ');
|
|
135
|
+
this.error(`File(s) exceed maximum size of ${formatBytes(MAX_SINGLE_FILE_BYTES)}: ${offenders}`);
|
|
136
|
+
}
|
|
137
|
+
const traversalPaths = files.filter(f => f.path.includes('..'));
|
|
138
|
+
if (traversalPaths.length > 0) {
|
|
139
|
+
const offenders = traversalPaths.map(f => f.path).join(', ');
|
|
140
|
+
this.error(`Path traversal detected in file path(s): ${offenders}`);
|
|
141
|
+
}
|
|
142
|
+
const absolutePaths = files.filter(f => f.path.startsWith('/'));
|
|
143
|
+
if (absolutePaths.length > 0) {
|
|
144
|
+
const offenders = absolutePaths.map(f => f.path).join(', ');
|
|
145
|
+
this.error(`Absolute path(s) not allowed: ${offenders}`);
|
|
146
|
+
}
|
|
147
|
+
try {
|
|
148
|
+
const service = new HyperdriveSigV4Service(flags.domain);
|
|
149
|
+
// 4. Create static deployment (get presigned URLs)
|
|
150
|
+
const createSpinner = ora('Creating deployment and generating upload URLs...').start();
|
|
151
|
+
const { deploymentId, uploadUrls } = await service.deploymentCreateStatic({
|
|
152
|
+
commit: flags.commit,
|
|
153
|
+
deploymentName: flags.name,
|
|
154
|
+
files: files.map((f) => ({ contentType: f.contentType, path: f.path, size: f.size })),
|
|
155
|
+
projectSlug: flags.moduleSlug,
|
|
156
|
+
stage: flags.stage,
|
|
157
|
+
});
|
|
158
|
+
createSpinner.succeed('Deployment created');
|
|
159
|
+
// 5. Upload files in parallel via presigned URLs with retry
|
|
160
|
+
const uploadSpinner = ora(`Uploading files... 0/${files.length}`).start();
|
|
161
|
+
const urlMap = new Map(uploadUrls.map((u) => [u.path, u.url]));
|
|
162
|
+
let uploaded = 0;
|
|
163
|
+
let retriedCount = 0;
|
|
164
|
+
/**
|
|
165
|
+
* Upload a single file to its presigned URL.
|
|
166
|
+
* Throws on HTTP errors to let retryWithBackoff classify them.
|
|
167
|
+
*/
|
|
168
|
+
const uploadFile = async (localPath, url, contentType) => {
|
|
169
|
+
const body = readFileSync(localPath);
|
|
170
|
+
const response = await fetch(url, {
|
|
171
|
+
body,
|
|
172
|
+
headers: { 'Content-Type': contentType },
|
|
173
|
+
method: 'PUT',
|
|
174
|
+
});
|
|
175
|
+
if (!response.ok) {
|
|
176
|
+
const err = new Error(`Upload failed: HTTP ${response.status}`);
|
|
177
|
+
err.status = response.status;
|
|
178
|
+
throw err;
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
const results = await Promise.all(files.map(async (entry) => {
|
|
182
|
+
const url = urlMap.get(entry.path);
|
|
183
|
+
if (!url) {
|
|
184
|
+
return { attempts: 0, error: 'No presigned URL received', path: entry.path, success: false };
|
|
185
|
+
}
|
|
186
|
+
try {
|
|
187
|
+
const { attempts } = await retryWithBackoff(() => uploadFile(entry.localPath, url, entry.contentType), { backoffMultiplier: 2, baseDelayMs: 1000, maxRetries: 3 });
|
|
188
|
+
uploaded++;
|
|
189
|
+
if (attempts > 1)
|
|
190
|
+
retriedCount++;
|
|
191
|
+
uploadSpinner.text = `Uploading files... ${uploaded}/${files.length}`;
|
|
192
|
+
return { attempts, path: entry.path, success: true };
|
|
193
|
+
}
|
|
194
|
+
catch (error) {
|
|
195
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
196
|
+
return { attempts: 4, error: errMsg, path: entry.path, success: false };
|
|
197
|
+
}
|
|
198
|
+
}));
|
|
199
|
+
const failedResults = results.filter((r) => !r.success);
|
|
200
|
+
if (failedResults.length > 0) {
|
|
201
|
+
uploadSpinner.fail(`Upload failed for ${failedResults.length} file(s) after retries`);
|
|
202
|
+
for (const item of failedResults) {
|
|
203
|
+
this.log(chalk.red(` ${item.path}: ${item.error}`));
|
|
204
|
+
}
|
|
205
|
+
this.exit(1);
|
|
206
|
+
}
|
|
207
|
+
const successMsg = retriedCount > 0
|
|
208
|
+
? `Uploaded ${uploaded}/${files.length} files (${retriedCount} required retries)`
|
|
209
|
+
: `Uploaded ${uploaded}/${files.length} files`;
|
|
210
|
+
uploadSpinner.succeed(successMsg);
|
|
211
|
+
// 6. Finalize deployment
|
|
212
|
+
const finalizeSpinner = ora('Finalizing deployment...').start();
|
|
213
|
+
const { url } = await service.deploymentFinalizeStatic(deploymentId, { spa: flags.spa });
|
|
214
|
+
finalizeSpinner.succeed('CloudFront invalidation created');
|
|
215
|
+
this.log('');
|
|
216
|
+
this.log(chalk.green(`✅ Deployed to ${chalk.cyan(url)}`));
|
|
217
|
+
this.log(chalk.gray(` Deployment: ${flags.name}`));
|
|
218
|
+
}
|
|
219
|
+
catch (error) {
|
|
220
|
+
const err = error;
|
|
221
|
+
// Ignore EEXIT with code 0 - oclif clean exit
|
|
222
|
+
if (err.message?.includes('EEXIT') && err.oclif?.exit === 0) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
if (err.response?.status === 400) {
|
|
226
|
+
this.log(chalk.red(`❌ ${err.response.data || err.message}`));
|
|
227
|
+
this.log(chalk.gray(' Hint: ensure the module is configured for static deployment'));
|
|
228
|
+
this.log(chalk.gray(' Run: hd module update <slug> --deploymentStrategy static'));
|
|
229
|
+
}
|
|
230
|
+
else if (err.response?.status === 404) {
|
|
231
|
+
this.log(chalk.red(`❌ Project or stage not found: ${err.response.data || err.message}`));
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
this.log(chalk.red(`❌ Error: ${err.message}`));
|
|
235
|
+
}
|
|
236
|
+
this.exit(1);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
@@ -80,14 +80,22 @@ export default class DeploymentCreate extends Command {
|
|
|
80
80
|
this.log('');
|
|
81
81
|
try {
|
|
82
82
|
const service = new HyperdriveSigV4Service(flags.domain);
|
|
83
|
-
|
|
83
|
+
// Fetch module to get moduleType (for CDK detection optimization)
|
|
84
|
+
const module = await service.moduleGet({ slug: flags.moduleSlug });
|
|
85
|
+
const moduleType = module.moduleType;
|
|
86
|
+
const deploymentPayload = {
|
|
84
87
|
commit: flags.commit,
|
|
85
88
|
launch: flags.launch,
|
|
86
89
|
name: flags.name,
|
|
87
90
|
projectSlug: flags.moduleSlug, // API still expects projectSlug (backend terminology)
|
|
88
91
|
regions: flags.regions,
|
|
89
92
|
stage: flags.stage,
|
|
90
|
-
}
|
|
93
|
+
};
|
|
94
|
+
// Include moduleType only when present (omit for legacy modules → builder auto-detects)
|
|
95
|
+
if (moduleType) {
|
|
96
|
+
deploymentPayload.moduleType = moduleType;
|
|
97
|
+
}
|
|
98
|
+
const result = await service.deploymentCreate(deploymentPayload);
|
|
91
99
|
// Check if API returned logging config (for real-time streaming)
|
|
92
100
|
const loggingConfig = result.logging;
|
|
93
101
|
if (flags.noStream || !loggingConfig) {
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Args, Command } from '@oclif/core';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { HyperdriveSigV4Service } from '../../services/hyperdrive-sigv4.js';
|
|
4
|
+
export default class DomainSetProduction extends Command {
|
|
5
|
+
static args = {
|
|
6
|
+
domain: Args.string({
|
|
7
|
+
description: 'The production domain to register (e.g. acme.com)',
|
|
8
|
+
required: true,
|
|
9
|
+
}),
|
|
10
|
+
};
|
|
11
|
+
static description = 'Set or update the production domain for the current tenant';
|
|
12
|
+
static examples = [
|
|
13
|
+
'<%= config.bin %> <%= command.id %> acme.com',
|
|
14
|
+
];
|
|
15
|
+
async run() {
|
|
16
|
+
const { args } = await this.parse(DomainSetProduction);
|
|
17
|
+
const api = new HyperdriveSigV4Service();
|
|
18
|
+
this.log(chalk.gray(`Setting production domain to ${chalk.cyan(args.domain)}...`));
|
|
19
|
+
const result = await api.domainSetProduction({ domain: args.domain });
|
|
20
|
+
this.log('');
|
|
21
|
+
this.log(chalk.green(`Production domain set: ${chalk.cyan(result.domain)}`));
|
|
22
|
+
this.log(chalk.gray(` Status: ${result.status}`));
|
|
23
|
+
this.log(chalk.gray(` Tenant: ${result.tenantId}`));
|
|
24
|
+
this.log('');
|
|
25
|
+
this.log(chalk.gray('DNS validation will begin automatically. Check status with `hd domain current`.'));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class ListOpenPrs extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
7
|
+
provider: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
repo: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
token: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
};
|
|
11
|
+
run(): Promise<void>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { Command, Flags } from '@oclif/core';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { getProviderByName } from '../../lib/git-providers/index.js';
|
|
4
|
+
import { printTable } from '../../utils/table.js';
|
|
5
|
+
export default class ListOpenPrs extends Command {
|
|
6
|
+
static description = 'List open pull requests (GitHub) or merge requests (GitLab) for a repository';
|
|
7
|
+
static examples = [
|
|
8
|
+
'<%= config.bin %> git list-open-prs --provider=github --repo=devsquad/super-repo',
|
|
9
|
+
'<%= config.bin %> git list-open-prs --provider=gitlab --repo=dev_squad/repo/web-apps/sign',
|
|
10
|
+
'<%= config.bin %> git list-open-prs --provider=github --repo=devsquad/super-repo --json',
|
|
11
|
+
'<%= config.bin %> git list-open-prs --provider=gitlab --repo=dev_squad/repo/web-apps/sign --token=glpat-xxx',
|
|
12
|
+
];
|
|
13
|
+
static flags = {
|
|
14
|
+
json: Flags.boolean({
|
|
15
|
+
default: false,
|
|
16
|
+
description: 'Output in JSON format',
|
|
17
|
+
}),
|
|
18
|
+
provider: Flags.string({
|
|
19
|
+
char: 'p',
|
|
20
|
+
description: 'Git provider',
|
|
21
|
+
options: ['github', 'gitlab'],
|
|
22
|
+
required: true,
|
|
23
|
+
}),
|
|
24
|
+
repo: Flags.string({
|
|
25
|
+
char: 'r',
|
|
26
|
+
description: 'Repository (owner/repo for GitHub, project path for GitLab)',
|
|
27
|
+
required: true,
|
|
28
|
+
}),
|
|
29
|
+
token: Flags.string({
|
|
30
|
+
char: 't',
|
|
31
|
+
description: 'API token (overrides GITHUB_TOKEN / GITLAB_TOKEN env var)',
|
|
32
|
+
}),
|
|
33
|
+
};
|
|
34
|
+
async run() {
|
|
35
|
+
const { flags } = await this.parse(ListOpenPrs);
|
|
36
|
+
const provider = getProviderByName(flags.provider);
|
|
37
|
+
const providerLabel = flags.provider === 'github' ? 'GitHub PRs' : 'GitLab MRs';
|
|
38
|
+
try {
|
|
39
|
+
const pullRequests = await provider.listOpenPullRequests({
|
|
40
|
+
repo: flags.repo,
|
|
41
|
+
token: flags.token,
|
|
42
|
+
});
|
|
43
|
+
if (flags.json) {
|
|
44
|
+
this.log(JSON.stringify(pullRequests, null, 2));
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (pullRequests.length === 0) {
|
|
48
|
+
this.log(chalk.yellow(`\nNo open ${providerLabel} found for ${flags.repo}`));
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
this.log(chalk.blue(`\nOpen ${providerLabel} for ${chalk.bold(flags.repo)} (${pullRequests.length}):\n`));
|
|
52
|
+
const tableData = pullRequests.map(pr => ({
|
|
53
|
+
author: pr.author,
|
|
54
|
+
number: pr.number,
|
|
55
|
+
sourceBranch: pr.sourceBranch,
|
|
56
|
+
title: pr.title,
|
|
57
|
+
url: pr.url,
|
|
58
|
+
}));
|
|
59
|
+
printTable(tableData, {
|
|
60
|
+
number: {
|
|
61
|
+
header: '#',
|
|
62
|
+
get: (row) => chalk.cyan(String(row.number)),
|
|
63
|
+
},
|
|
64
|
+
title: {
|
|
65
|
+
header: 'Title',
|
|
66
|
+
get: (row) => row.title.length > 60 ? `${row.title.slice(0, 57)}...` : row.title,
|
|
67
|
+
},
|
|
68
|
+
sourceBranch: {
|
|
69
|
+
header: 'Branch',
|
|
70
|
+
get: (row) => chalk.green(row.sourceBranch),
|
|
71
|
+
},
|
|
72
|
+
author: {
|
|
73
|
+
header: 'Author',
|
|
74
|
+
get: (row) => chalk.gray(row.author),
|
|
75
|
+
},
|
|
76
|
+
url: {
|
|
77
|
+
header: 'URL',
|
|
78
|
+
get: (row) => chalk.gray(row.url),
|
|
79
|
+
},
|
|
80
|
+
}, (msg) => this.log(msg));
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
84
|
+
this.error(`Failed to list open ${providerLabel}: ${errorMessage}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class HookAdd extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
'action-config': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
'action-type': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
conditions: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
cron: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
domain: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
event: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
'flexible-window': import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
14
|
+
order: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
|
+
timezone: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
16
|
+
};
|
|
17
|
+
run(): Promise<void>;
|
|
18
|
+
private handleApiError;
|
|
19
|
+
private printSuccess;
|
|
20
|
+
private runInteractive;
|
|
21
|
+
private runNonInteractive;
|
|
22
|
+
}
|