@agentuity/cli 1.0.14 → 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/dist/agent-detection.js +2 -2
- package/dist/agent-detection.js.map +1 -1
- package/dist/banner.js +2 -2
- package/dist/banner.js.map +1 -1
- package/dist/bun-path.d.ts.map +1 -1
- package/dist/bun-path.js +2 -1
- package/dist/bun-path.js.map +1 -1
- package/dist/cmd/build/ast.d.ts.map +1 -1
- package/dist/cmd/build/ast.js +87 -14
- package/dist/cmd/build/ast.js.map +1 -1
- package/dist/cmd/build/entry-generator.d.ts.map +1 -1
- package/dist/cmd/build/entry-generator.js +5 -2
- package/dist/cmd/build/entry-generator.js.map +1 -1
- package/dist/cmd/build/vite/agent-discovery.d.ts.map +1 -1
- package/dist/cmd/build/vite/agent-discovery.js +3 -2
- package/dist/cmd/build/vite/agent-discovery.js.map +1 -1
- package/dist/cmd/build/vite/metadata-generator.d.ts.map +1 -1
- package/dist/cmd/build/vite/metadata-generator.js +3 -38
- package/dist/cmd/build/vite/metadata-generator.js.map +1 -1
- package/dist/cmd/build/vite/registry-generator.d.ts.map +1 -1
- package/dist/cmd/build/vite/registry-generator.js +24 -9
- package/dist/cmd/build/vite/registry-generator.js.map +1 -1
- package/dist/cmd/build/vite/route-discovery.d.ts.map +1 -1
- package/dist/cmd/build/vite/route-discovery.js +3 -2
- package/dist/cmd/build/vite/route-discovery.js.map +1 -1
- package/dist/cmd/build/vite/vite-asset-server-config.js +1 -1
- package/dist/cmd/build/vite/vite-asset-server-config.js.map +1 -1
- package/dist/cmd/build/vite/vite-asset-server.d.ts.map +1 -1
- package/dist/cmd/build/vite/vite-asset-server.js +42 -39
- package/dist/cmd/build/vite/vite-asset-server.js.map +1 -1
- package/dist/cmd/cloud/deploy.d.ts.map +1 -1
- package/dist/cmd/cloud/deploy.js +19 -21
- package/dist/cmd/cloud/deploy.js.map +1 -1
- package/dist/cmd/cloud/env/delete.d.ts.map +1 -1
- package/dist/cmd/cloud/env/delete.js +3 -26
- package/dist/cmd/cloud/env/delete.js.map +1 -1
- package/dist/cmd/cloud/env/import.d.ts.map +1 -1
- package/dist/cmd/cloud/env/import.js +3 -16
- package/dist/cmd/cloud/env/import.js.map +1 -1
- package/dist/cmd/cloud/env/set.d.ts.map +1 -1
- package/dist/cmd/cloud/env/set.js +3 -19
- package/dist/cmd/cloud/env/set.js.map +1 -1
- package/dist/cmd/cloud/sandbox/cp.d.ts.map +1 -1
- package/dist/cmd/cloud/sandbox/cp.js +2 -1
- package/dist/cmd/cloud/sandbox/cp.js.map +1 -1
- package/dist/cmd/cloud/sandbox/get.d.ts.map +1 -1
- package/dist/cmd/cloud/sandbox/get.js +5 -0
- package/dist/cmd/cloud/sandbox/get.js.map +1 -1
- package/dist/cmd/cloud/sandbox/snapshot/build.d.ts.map +1 -1
- package/dist/cmd/cloud/sandbox/snapshot/build.js +32 -1
- package/dist/cmd/cloud/sandbox/snapshot/build.js.map +1 -1
- package/dist/cmd/cloud/sandbox/snapshot/generate.d.ts.map +1 -1
- package/dist/cmd/cloud/sandbox/snapshot/generate.js +7 -0
- package/dist/cmd/cloud/sandbox/snapshot/generate.js.map +1 -1
- package/dist/cmd/cloud/storage/create.d.ts.map +1 -1
- package/dist/cmd/cloud/storage/create.js +58 -26
- package/dist/cmd/cloud/storage/create.js.map +1 -1
- package/dist/cmd/cloud/storage/delete.d.ts.map +1 -1
- package/dist/cmd/cloud/storage/delete.js +108 -16
- package/dist/cmd/cloud/storage/delete.js.map +1 -1
- package/dist/cmd/git/account/add.d.ts +6 -8
- package/dist/cmd/git/account/add.d.ts.map +1 -1
- package/dist/cmd/git/account/add.js +37 -151
- package/dist/cmd/git/account/add.js.map +1 -1
- package/dist/cmd/git/account/index.d.ts +0 -1
- package/dist/cmd/git/account/index.d.ts.map +1 -1
- package/dist/cmd/git/account/index.js +3 -4
- package/dist/cmd/git/account/index.js.map +1 -1
- package/dist/cmd/git/account/list.d.ts.map +1 -1
- package/dist/cmd/git/account/list.js +35 -67
- package/dist/cmd/git/account/list.js.map +1 -1
- package/dist/cmd/git/account/remove.d.ts.map +1 -1
- package/dist/cmd/git/account/remove.js +42 -84
- package/dist/cmd/git/account/remove.js.map +1 -1
- package/dist/cmd/git/api.d.ts +19 -23
- package/dist/cmd/git/api.d.ts.map +1 -1
- package/dist/cmd/git/api.js +38 -56
- package/dist/cmd/git/api.js.map +1 -1
- package/dist/cmd/git/identity/connect.d.ts +15 -0
- package/dist/cmd/git/identity/connect.d.ts.map +1 -0
- package/dist/cmd/git/identity/connect.js +135 -0
- package/dist/cmd/git/identity/connect.js.map +1 -0
- package/dist/cmd/git/identity/disconnect.d.ts +2 -0
- package/dist/cmd/git/identity/disconnect.d.ts.map +1 -0
- package/dist/cmd/git/identity/disconnect.js +83 -0
- package/dist/cmd/git/identity/disconnect.js.map +1 -0
- package/dist/cmd/git/identity/index.d.ts +2 -0
- package/dist/cmd/git/identity/index.d.ts.map +1 -0
- package/dist/cmd/git/identity/index.js +10 -0
- package/dist/cmd/git/identity/index.js.map +1 -0
- package/dist/cmd/git/identity/status.d.ts +2 -0
- package/dist/cmd/git/identity/status.d.ts.map +1 -0
- package/dist/cmd/git/identity/status.js +77 -0
- package/dist/cmd/git/identity/status.js.map +1 -0
- package/dist/cmd/git/index.d.ts +0 -1
- package/dist/cmd/git/index.d.ts.map +1 -1
- package/dist/cmd/git/index.js +3 -2
- package/dist/cmd/git/index.js.map +1 -1
- package/dist/cmd/git/link.d.ts +2 -3
- package/dist/cmd/git/link.d.ts.map +1 -1
- package/dist/cmd/git/link.js +22 -28
- package/dist/cmd/git/link.js.map +1 -1
- package/dist/cmd/git/list.d.ts.map +1 -1
- package/dist/cmd/git/list.js +42 -55
- package/dist/cmd/git/list.js.map +1 -1
- package/dist/cmd/git/status.d.ts.map +1 -1
- package/dist/cmd/git/status.js +51 -38
- package/dist/cmd/git/status.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +14 -4
- package/dist/config.js.map +1 -1
- package/dist/utils/detectSubagent.d.ts.map +1 -1
- package/dist/utils/detectSubagent.js +3 -0
- package/dist/utils/detectSubagent.js.map +1 -1
- package/dist/utils/normalize-path.d.ts +11 -0
- package/dist/utils/normalize-path.d.ts.map +1 -0
- package/dist/utils/normalize-path.js +13 -0
- package/dist/utils/normalize-path.js.map +1 -0
- package/dist/utils/zip.d.ts.map +1 -1
- package/dist/utils/zip.js +2 -1
- package/dist/utils/zip.js.map +1 -1
- package/package.json +6 -6
- package/src/agent-detection.ts +2 -2
- package/src/banner.ts +2 -2
- package/src/bun-path.ts +2 -1
- package/src/cmd/build/ast.ts +96 -15
- package/src/cmd/build/entry-generator.ts +6 -2
- package/src/cmd/build/vite/agent-discovery.ts +3 -2
- package/src/cmd/build/vite/metadata-generator.ts +3 -40
- package/src/cmd/build/vite/registry-generator.ts +26 -9
- package/src/cmd/build/vite/route-discovery.ts +3 -2
- package/src/cmd/build/vite/vite-asset-server-config.ts +1 -1
- package/src/cmd/build/vite/vite-asset-server.ts +53 -40
- package/src/cmd/cloud/deploy.ts +54 -61
- package/src/cmd/cloud/env/delete.ts +3 -34
- package/src/cmd/cloud/env/import.ts +2 -18
- package/src/cmd/cloud/env/set.ts +2 -21
- package/src/cmd/cloud/sandbox/cp.ts +2 -1
- package/src/cmd/cloud/sandbox/get.ts +5 -0
- package/src/cmd/cloud/sandbox/snapshot/build.ts +41 -0
- package/src/cmd/cloud/sandbox/snapshot/generate.ts +7 -0
- package/src/cmd/cloud/storage/create.ts +62 -27
- package/src/cmd/cloud/storage/delete.ts +136 -17
- package/src/cmd/git/account/add.ts +51 -190
- package/src/cmd/git/account/index.ts +3 -5
- package/src/cmd/git/account/list.ts +51 -82
- package/src/cmd/git/account/remove.ts +45 -95
- package/src/cmd/git/api.ts +49 -111
- package/src/cmd/git/identity/connect.ts +178 -0
- package/src/cmd/git/identity/disconnect.ts +103 -0
- package/src/cmd/git/identity/index.ts +10 -0
- package/src/cmd/git/identity/status.ts +96 -0
- package/src/cmd/git/index.ts +3 -3
- package/src/cmd/git/link.ts +32 -35
- package/src/cmd/git/list.ts +48 -59
- package/src/cmd/git/status.ts +55 -40
- package/src/config.ts +14 -5
- package/src/utils/detectSubagent.ts +5 -0
- package/src/utils/normalize-path.ts +12 -0
- package/src/utils/zip.ts +2 -1
|
@@ -40,6 +40,7 @@ const SandboxGetResponseSchema = z.object({
|
|
|
40
40
|
stdoutStreamUrl: z.string().optional().describe('URL to stdout output stream'),
|
|
41
41
|
stderrStreamUrl: z.string().optional().describe('URL to stderr output stream'),
|
|
42
42
|
dependencies: z.array(z.string()).optional().describe('Apt packages installed'),
|
|
43
|
+
packages: z.array(z.string()).optional().describe('npm/bun packages installed globally'),
|
|
43
44
|
metadata: z.record(z.string(), z.unknown()).optional().describe('User-defined metadata'),
|
|
44
45
|
resources: SandboxResourcesSchema.optional().describe('Resource limits'),
|
|
45
46
|
url: z.string().optional().describe('Public URL for the sandbox (if network port configured)'),
|
|
@@ -138,6 +139,9 @@ export const getSubcommand = createCommand({
|
|
|
138
139
|
if (result.dependencies && result.dependencies.length > 0) {
|
|
139
140
|
tableData['Dependencies'] = result.dependencies.join(', ');
|
|
140
141
|
}
|
|
142
|
+
if (result.packages && result.packages.length > 0) {
|
|
143
|
+
tableData['Packages'] = result.packages.join(', ');
|
|
144
|
+
}
|
|
141
145
|
if (resourceParts.length > 0) {
|
|
142
146
|
tableData['Resources'] = resourceParts.join(', ');
|
|
143
147
|
}
|
|
@@ -164,6 +168,7 @@ export const getSubcommand = createCommand({
|
|
|
164
168
|
stdoutStreamUrl: result.stdoutStreamUrl,
|
|
165
169
|
stderrStreamUrl: result.stderrStreamUrl,
|
|
166
170
|
dependencies: result.dependencies,
|
|
171
|
+
packages: result.packages,
|
|
167
172
|
metadata: result.metadata,
|
|
168
173
|
resources: result.resources,
|
|
169
174
|
url: result.url,
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
snapshotBuildFinalize,
|
|
13
13
|
snapshotUpload,
|
|
14
14
|
SnapshotBuildFileSchema,
|
|
15
|
+
NPM_PACKAGE_NAME_PATTERN,
|
|
15
16
|
} from '@agentuity/server';
|
|
16
17
|
import type { SnapshotFileInfo, SnapshotBuildGitInfo } from '@agentuity/server';
|
|
17
18
|
import { getCatalystAPIClient } from '../../../../config';
|
|
@@ -297,6 +298,7 @@ async function generateContentHash(params: {
|
|
|
297
298
|
runtime: string;
|
|
298
299
|
description?: string;
|
|
299
300
|
dependencies?: string[];
|
|
301
|
+
packages?: string[];
|
|
300
302
|
files: SnapshotFileInfo[];
|
|
301
303
|
fileHashes: Map<string, string>;
|
|
302
304
|
env?: Record<string, string>;
|
|
@@ -315,6 +317,11 @@ async function generateContentHash(params: {
|
|
|
315
317
|
hash.update(`dependencies:${sortedDeps.join(',')}\n`);
|
|
316
318
|
}
|
|
317
319
|
|
|
320
|
+
if (params.packages && params.packages.length > 0) {
|
|
321
|
+
const sortedPkgs = [...params.packages].sort();
|
|
322
|
+
hash.update(`packages:${sortedPkgs.join(',')}\n`);
|
|
323
|
+
}
|
|
324
|
+
|
|
318
325
|
if (params.files.length > 0) {
|
|
319
326
|
const sortedFiles = [...params.files].sort((a, b) => a.path.localeCompare(b.path));
|
|
320
327
|
for (const file of sortedFiles) {
|
|
@@ -575,6 +582,22 @@ export const buildSubcommand = createCommand({
|
|
|
575
582
|
}
|
|
576
583
|
}
|
|
577
584
|
|
|
585
|
+
if (buildConfig.packages && buildConfig.packages.length > 0) {
|
|
586
|
+
// Validate package specifiers using shared pattern (also enforced at schema level)
|
|
587
|
+
const invalidPackages = buildConfig.packages.filter(
|
|
588
|
+
(pkg) => !NPM_PACKAGE_NAME_PATTERN.test(pkg)
|
|
589
|
+
);
|
|
590
|
+
if (invalidPackages.length > 0) {
|
|
591
|
+
tui.error('Invalid package specifiers:');
|
|
592
|
+
for (const pkg of invalidPackages) {
|
|
593
|
+
tui.bullet(
|
|
594
|
+
`${pkg}: must not contain whitespace, semicolons, backticks, pipes, or dollar signs`
|
|
595
|
+
);
|
|
596
|
+
}
|
|
597
|
+
process.exit(1);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
578
601
|
let files = new Map<string, FileEntry>();
|
|
579
602
|
if (buildConfig.files && buildConfig.files.length > 0) {
|
|
580
603
|
files = await resolveFileGlobs(directory, buildConfig.files);
|
|
@@ -613,6 +636,7 @@ export const buildSubcommand = createCommand({
|
|
|
613
636
|
runtime: buildConfig.runtime,
|
|
614
637
|
description: finalDescription,
|
|
615
638
|
dependencies: buildConfig.dependencies,
|
|
639
|
+
packages: buildConfig.packages,
|
|
616
640
|
files: fileList,
|
|
617
641
|
fileHashes,
|
|
618
642
|
env: finalEnv,
|
|
@@ -647,6 +671,14 @@ export const buildSubcommand = createCommand({
|
|
|
647
671
|
}
|
|
648
672
|
}
|
|
649
673
|
|
|
674
|
+
if (buildConfig.packages && buildConfig.packages.length > 0) {
|
|
675
|
+
console.log('');
|
|
676
|
+
tui.info('Packages (npm/bun):');
|
|
677
|
+
for (const pkg of buildConfig.packages) {
|
|
678
|
+
console.log(` ${tui.muted('•')} ${pkg}`);
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
|
|
650
682
|
if (finalEnv && Object.keys(finalEnv).length > 0) {
|
|
651
683
|
console.log('');
|
|
652
684
|
tui.info('Environment:');
|
|
@@ -914,6 +946,7 @@ export const buildSubcommand = createCommand({
|
|
|
914
946
|
fileCount: fileList.length,
|
|
915
947
|
files: fileList,
|
|
916
948
|
dependencies: buildConfig.dependencies,
|
|
949
|
+
packages: buildConfig.packages,
|
|
917
950
|
env: finalEnv,
|
|
918
951
|
metadata: finalMetadata,
|
|
919
952
|
orgId,
|
|
@@ -949,6 +982,14 @@ export const buildSubcommand = createCommand({
|
|
|
949
982
|
}
|
|
950
983
|
}
|
|
951
984
|
|
|
985
|
+
if (buildConfig.packages && buildConfig.packages.length > 0) {
|
|
986
|
+
console.log('');
|
|
987
|
+
tui.info('Packages (npm/bun):');
|
|
988
|
+
for (const pkg of buildConfig.packages) {
|
|
989
|
+
console.log(` ${tui.muted('•')} ${pkg}`);
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
|
|
952
993
|
if (finalEnv && Object.keys(finalEnv).length > 0) {
|
|
953
994
|
console.log('');
|
|
954
995
|
tui.info('Environment:');
|
|
@@ -35,6 +35,12 @@ description: My sandbox snapshot
|
|
|
35
35
|
# - ffmpeg
|
|
36
36
|
# - imagemagick=8:6.9*
|
|
37
37
|
|
|
38
|
+
# Optional: npm/bun packages to install globally
|
|
39
|
+
# Installed via 'bun install -g' at sandbox startup
|
|
40
|
+
# packages:
|
|
41
|
+
# - opencode-ai
|
|
42
|
+
# - typescript
|
|
43
|
+
|
|
38
44
|
# Optional: Files to include from the build context directory
|
|
39
45
|
# Supports glob patterns and negative patterns (prefix with !)
|
|
40
46
|
# Files are placed in /home/agentuity/ in the sandbox
|
|
@@ -68,6 +74,7 @@ const TEMPLATE_JSON = {
|
|
|
68
74
|
name: 'my-snapshot',
|
|
69
75
|
description: 'My sandbox snapshot',
|
|
70
76
|
dependencies: ['curl'],
|
|
77
|
+
packages: ['opencode-ai'],
|
|
71
78
|
files: ['src/**', '*.js', '!**/*.test.js'],
|
|
72
79
|
env: {
|
|
73
80
|
NODE_ENV: 'production',
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { createResources } from '@agentuity/server';
|
|
2
|
+
import { createResources, APIError, validateBucketName } from '@agentuity/server';
|
|
3
3
|
import { createSubcommand as defineSubcommand } from '../../../types';
|
|
4
4
|
import * as tui from '../../../tui';
|
|
5
5
|
import { getCatalystAPIClient } from '../../../config';
|
|
6
6
|
import { getCommand } from '../../../command-prefix';
|
|
7
7
|
import { isDryRunMode, outputDryRun } from '../../../explain';
|
|
8
|
+
import { ErrorCode } from '../../../errors';
|
|
8
9
|
import { addResourceEnvVars } from '../../../env-util';
|
|
9
10
|
|
|
10
11
|
export const createSubcommand = defineSubcommand({
|
|
@@ -23,6 +24,10 @@ export const createSubcommand = defineSubcommand({
|
|
|
23
24
|
command: getCommand('cloud storage new'),
|
|
24
25
|
description: 'Alias for "cloud storage create" (shorthand "new")',
|
|
25
26
|
},
|
|
27
|
+
{
|
|
28
|
+
command: getCommand('cloud storage create --name my-bucket'),
|
|
29
|
+
description: 'Create a new cloud storage bucket with a custom name',
|
|
30
|
+
},
|
|
26
31
|
{
|
|
27
32
|
command: getCommand('--dry-run cloud storage create'),
|
|
28
33
|
description: 'Dry-run: display what would be created without making changes',
|
|
@@ -30,6 +35,7 @@ export const createSubcommand = defineSubcommand({
|
|
|
30
35
|
],
|
|
31
36
|
schema: {
|
|
32
37
|
options: z.object({
|
|
38
|
+
name: z.string().optional().describe('Custom bucket name'),
|
|
33
39
|
description: z.string().optional().describe('Optional description for the bucket'),
|
|
34
40
|
}),
|
|
35
41
|
response: z.object({
|
|
@@ -41,9 +47,22 @@ export const createSubcommand = defineSubcommand({
|
|
|
41
47
|
async handler(ctx) {
|
|
42
48
|
const { logger, orgId, region, auth, options, opts } = ctx;
|
|
43
49
|
|
|
50
|
+
// Validate bucket name if provided
|
|
51
|
+
if (opts.name) {
|
|
52
|
+
const validation = validateBucketName(opts.name);
|
|
53
|
+
if (!validation.valid) {
|
|
54
|
+
tui.fatal(validation.error!, ErrorCode.INVALID_ARGUMENT);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
44
58
|
// Handle dry-run mode
|
|
45
59
|
if (isDryRunMode(options)) {
|
|
46
|
-
|
|
60
|
+
let message = opts.name
|
|
61
|
+
? `Would create storage bucket with name: ${opts.name} in region: ${region}`
|
|
62
|
+
: `Would create storage bucket in region: ${region}`;
|
|
63
|
+
if (opts.description) {
|
|
64
|
+
message += ` with description: "${opts.description}"`;
|
|
65
|
+
}
|
|
47
66
|
outputDryRun(message, options);
|
|
48
67
|
if (!options.json) {
|
|
49
68
|
tui.newline();
|
|
@@ -51,41 +70,57 @@ export const createSubcommand = defineSubcommand({
|
|
|
51
70
|
}
|
|
52
71
|
return {
|
|
53
72
|
success: false,
|
|
54
|
-
name: 'dry-run-bucket',
|
|
73
|
+
name: opts.name || 'dry-run-bucket',
|
|
55
74
|
};
|
|
56
75
|
}
|
|
57
76
|
|
|
58
77
|
const catalystClient = getCatalystAPIClient(logger, auth, region);
|
|
59
78
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
79
|
+
try {
|
|
80
|
+
const created = await tui.spinner({
|
|
81
|
+
message: `Creating storage in ${region}`,
|
|
82
|
+
clearOnSuccess: true,
|
|
83
|
+
callback: async () => {
|
|
84
|
+
return createResources(catalystClient, orgId, region!, [
|
|
85
|
+
{ type: 's3', name: opts.name, description: opts.description },
|
|
86
|
+
]);
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const resource = created[0];
|
|
91
|
+
if (resource) {
|
|
92
|
+
// Write environment variables to .env if running inside a project
|
|
93
|
+
if (ctx.projectDir && resource.env && Object.keys(resource.env).length > 0) {
|
|
94
|
+
await addResourceEnvVars(ctx.projectDir, resource.env);
|
|
95
|
+
if (!options.json) {
|
|
96
|
+
tui.info('Environment variables written to .env');
|
|
97
|
+
}
|
|
98
|
+
}
|
|
69
99
|
|
|
70
|
-
const resource = created[0];
|
|
71
|
-
if (resource) {
|
|
72
|
-
// Write environment variables to .env if running inside a project
|
|
73
|
-
if (ctx.projectDir && resource.env && Object.keys(resource.env).length > 0) {
|
|
74
|
-
await addResourceEnvVars(ctx.projectDir, resource.env);
|
|
75
100
|
if (!options.json) {
|
|
76
|
-
tui.
|
|
101
|
+
tui.success(`Created storage: ${tui.bold(resource.name)}`);
|
|
77
102
|
}
|
|
103
|
+
return {
|
|
104
|
+
success: true,
|
|
105
|
+
name: resource.name,
|
|
106
|
+
};
|
|
107
|
+
} else {
|
|
108
|
+
tui.fatal('Failed to create storage');
|
|
78
109
|
}
|
|
79
|
-
|
|
80
|
-
if (
|
|
81
|
-
|
|
110
|
+
} catch (ex) {
|
|
111
|
+
if (ex instanceof APIError) {
|
|
112
|
+
if (ex.status === 409) {
|
|
113
|
+
const bucketName = opts.name || 'auto-generated';
|
|
114
|
+
tui.fatal(
|
|
115
|
+
`bucket with the name "${bucketName}" already exists. Use another name or don't specify --name for a unique name to be generated automatically.`,
|
|
116
|
+
ErrorCode.INVALID_ARGUMENT
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
if (ex.status === 400) {
|
|
120
|
+
tui.fatal(ex.message || 'invalid bucket name', ErrorCode.INVALID_ARGUMENT);
|
|
121
|
+
}
|
|
82
122
|
}
|
|
83
|
-
|
|
84
|
-
success: true,
|
|
85
|
-
name: resource.name,
|
|
86
|
-
};
|
|
87
|
-
} else {
|
|
88
|
-
tui.fatal('Failed to create storage');
|
|
123
|
+
throw ex;
|
|
89
124
|
}
|
|
90
125
|
},
|
|
91
126
|
});
|
|
@@ -14,7 +14,7 @@ import { getResourceInfo, setResourceInfo, deleteResourceRegion } from '../../..
|
|
|
14
14
|
export const deleteSubcommand = createSubcommand({
|
|
15
15
|
name: 'delete',
|
|
16
16
|
aliases: ['rm', 'del', 'remove'],
|
|
17
|
-
description: 'Delete a storage resource or
|
|
17
|
+
description: 'Delete a storage resource, file, or folder',
|
|
18
18
|
tags: ['destructive', 'deletes-resource', 'slow', 'requires-auth', 'requires-deployment'],
|
|
19
19
|
idempotent: false,
|
|
20
20
|
requires: { auth: true },
|
|
@@ -32,6 +32,10 @@ export const deleteSubcommand = createSubcommand({
|
|
|
32
32
|
command: getCommand('cloud storage delete'),
|
|
33
33
|
description: 'Interactive selection to delete a bucket',
|
|
34
34
|
},
|
|
35
|
+
{
|
|
36
|
+
command: getCommand('cloud storage delete my-bucket path/to/folder'),
|
|
37
|
+
description: 'Delete a folder and all its contents from a bucket',
|
|
38
|
+
},
|
|
35
39
|
{
|
|
36
40
|
command: getCommand('--dry-run cloud storage delete my-bucket'),
|
|
37
41
|
description: 'Dry-run: show what would be deleted without making changes',
|
|
@@ -48,6 +52,7 @@ export const deleteSubcommand = createSubcommand({
|
|
|
48
52
|
response: z.object({
|
|
49
53
|
success: z.boolean().describe('Whether deletion succeeded'),
|
|
50
54
|
name: z.string().describe('Deleted bucket or file name'),
|
|
55
|
+
count: z.number().optional().describe('Number of files deleted (for folder deletion)'),
|
|
51
56
|
}),
|
|
52
57
|
},
|
|
53
58
|
|
|
@@ -116,7 +121,7 @@ export const deleteSubcommand = createSubcommand({
|
|
|
116
121
|
bucketName = response.bucket;
|
|
117
122
|
}
|
|
118
123
|
|
|
119
|
-
// If filename is provided, delete the file from the bucket
|
|
124
|
+
// If filename is provided, delete the file or folder from the bucket
|
|
120
125
|
if (args.filename) {
|
|
121
126
|
const bucket = resources.s3.find((s3) => s3.bucket_name === bucketName);
|
|
122
127
|
|
|
@@ -131,22 +136,143 @@ export const deleteSubcommand = createSubcommand({
|
|
|
131
136
|
);
|
|
132
137
|
}
|
|
133
138
|
|
|
139
|
+
const s3Client = createS3Client({
|
|
140
|
+
endpoint: bucket.endpoint,
|
|
141
|
+
access_key: bucket.access_key,
|
|
142
|
+
secret_key: bucket.secret_key,
|
|
143
|
+
region: bucket.region,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
const filePath = args.filename;
|
|
147
|
+
|
|
148
|
+
// Check if path represents a folder by listing objects under it
|
|
149
|
+
const folderPrefix = filePath.endsWith('/') ? filePath : filePath + '/';
|
|
150
|
+
const folderContents: Array<{ key: string }> = [];
|
|
151
|
+
let continuationToken: string | undefined;
|
|
152
|
+
let isTruncated = false;
|
|
153
|
+
|
|
154
|
+
do {
|
|
155
|
+
const folderResult = await s3Client.list({
|
|
156
|
+
prefix: folderPrefix,
|
|
157
|
+
...(continuationToken ? { continuationToken } : {}),
|
|
158
|
+
});
|
|
159
|
+
if (folderResult.contents) {
|
|
160
|
+
folderContents.push(...folderResult.contents);
|
|
161
|
+
}
|
|
162
|
+
continuationToken = folderResult.nextContinuationToken;
|
|
163
|
+
isTruncated = folderResult.isTruncated ?? false;
|
|
164
|
+
} while (isTruncated);
|
|
165
|
+
|
|
166
|
+
if (folderContents.length > 0) {
|
|
167
|
+
// Path is a folder — recursive delete
|
|
168
|
+
const keysToDelete = folderContents.map((obj: { key: string }) => obj.key);
|
|
169
|
+
|
|
170
|
+
// Handle dry-run mode
|
|
171
|
+
if (isDryRunMode(options)) {
|
|
172
|
+
outputDryRun(
|
|
173
|
+
`Would delete ${keysToDelete.length} file${keysToDelete.length === 1 ? '' : 's'} under ${folderPrefix} from bucket ${bucketName}`,
|
|
174
|
+
options
|
|
175
|
+
);
|
|
176
|
+
if (!options.json) {
|
|
177
|
+
tui.newline();
|
|
178
|
+
tui.info('[DRY RUN] Folder deletion skipped');
|
|
179
|
+
}
|
|
180
|
+
return { success: false, name: filePath, count: keysToDelete.length };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Confirm
|
|
184
|
+
if (!opts.confirm) {
|
|
185
|
+
tui.warning(
|
|
186
|
+
`You are about to delete ${tui.bold(String(keysToDelete.length))} file${keysToDelete.length === 1 ? '' : 's'} under folder: ${tui.bold(folderPrefix)} from bucket: ${tui.bold(bucketName)}`
|
|
187
|
+
);
|
|
188
|
+
const confirm = await enquirer.prompt<{ confirm: boolean }>({
|
|
189
|
+
type: 'confirm',
|
|
190
|
+
name: 'confirm',
|
|
191
|
+
message: 'Are you sure you want to delete this folder and all its contents?',
|
|
192
|
+
initial: false,
|
|
193
|
+
});
|
|
194
|
+
if (!confirm.confirm) {
|
|
195
|
+
tui.info('Deletion cancelled');
|
|
196
|
+
return { success: false, name: filePath };
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Delete all files
|
|
201
|
+
await tui.spinner({
|
|
202
|
+
message: `Deleting ${keysToDelete.length} file${keysToDelete.length === 1 ? '' : 's'} under ${folderPrefix} from ${bucketName}`,
|
|
203
|
+
clearOnSuccess: true,
|
|
204
|
+
callback: async () => {
|
|
205
|
+
const errors: Array<{ key: string; error: string }> = [];
|
|
206
|
+
for (const key of keysToDelete) {
|
|
207
|
+
try {
|
|
208
|
+
await s3Client.delete(key);
|
|
209
|
+
} catch (err) {
|
|
210
|
+
errors.push({
|
|
211
|
+
key,
|
|
212
|
+
error: err instanceof Error ? err.message : String(err),
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (errors.length > 0) {
|
|
217
|
+
const failedKeys = errors.map((e) => e.key).join(', ');
|
|
218
|
+
throw new Error(
|
|
219
|
+
`Failed to delete ${errors.length} file${errors.length === 1 ? '' : 's'}: ${failedKeys}`
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
// Also delete the exact file if it exists (handles file+folder name conflicts)
|
|
226
|
+
// Skip if filePath was already deleted as part of folder contents (e.g., trailing-slash folder markers)
|
|
227
|
+
if (!keysToDelete.includes(filePath)) {
|
|
228
|
+
const exactFileCheck = await s3Client.list({ prefix: filePath });
|
|
229
|
+
const exactFile = (exactFileCheck.contents || []).find(
|
|
230
|
+
(obj: { key: string }) => obj.key === filePath
|
|
231
|
+
);
|
|
232
|
+
if (exactFile) {
|
|
233
|
+
await s3Client.delete(filePath);
|
|
234
|
+
keysToDelete.push(filePath);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (!options.json) {
|
|
239
|
+
tui.success(
|
|
240
|
+
`Deleted ${tui.bold(String(keysToDelete.length))} file${keysToDelete.length === 1 ? '' : 's'} under ${tui.bold(folderPrefix)} from ${tui.bold(bucketName)}`
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return { success: true, name: filePath, count: keysToDelete.length };
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Not a folder — check if exact file exists
|
|
248
|
+
const fileResult = await s3Client.list({ prefix: filePath });
|
|
249
|
+
const exactMatch = (fileResult.contents || []).find(
|
|
250
|
+
(obj: { key: string }) => obj.key === filePath
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
if (!exactMatch) {
|
|
254
|
+
tui.fatal(
|
|
255
|
+
`No file or folder found at '${filePath}' in bucket '${bucketName}'`,
|
|
256
|
+
ErrorCode.RESOURCE_NOT_FOUND
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
|
|
134
260
|
// Handle dry-run mode
|
|
135
261
|
if (isDryRunMode(options)) {
|
|
136
|
-
outputDryRun(`Would delete file ${
|
|
262
|
+
outputDryRun(`Would delete file ${filePath} from bucket ${bucketName}`, options);
|
|
137
263
|
if (!options.json) {
|
|
138
264
|
tui.newline();
|
|
139
265
|
tui.info('[DRY RUN] File deletion skipped');
|
|
140
266
|
}
|
|
141
267
|
return {
|
|
142
268
|
success: false,
|
|
143
|
-
name:
|
|
269
|
+
name: filePath,
|
|
144
270
|
};
|
|
145
271
|
}
|
|
146
272
|
|
|
147
273
|
if (!opts.confirm) {
|
|
148
274
|
tui.warning(
|
|
149
|
-
`You are about to delete file: ${tui.bold(
|
|
275
|
+
`You are about to delete file: ${tui.bold(filePath)} from bucket: ${tui.bold(bucketName)}`
|
|
150
276
|
);
|
|
151
277
|
|
|
152
278
|
const confirm = await enquirer.prompt<{ confirm: boolean }>({
|
|
@@ -158,32 +284,25 @@ export const deleteSubcommand = createSubcommand({
|
|
|
158
284
|
|
|
159
285
|
if (!confirm.confirm) {
|
|
160
286
|
tui.info('Deletion cancelled');
|
|
161
|
-
return { success: false, name:
|
|
287
|
+
return { success: false, name: filePath };
|
|
162
288
|
}
|
|
163
289
|
}
|
|
164
290
|
|
|
165
|
-
const s3Client = createS3Client({
|
|
166
|
-
endpoint: bucket.endpoint,
|
|
167
|
-
access_key: bucket.access_key,
|
|
168
|
-
secret_key: bucket.secret_key,
|
|
169
|
-
region: bucket.region,
|
|
170
|
-
});
|
|
171
|
-
|
|
172
291
|
await tui.spinner({
|
|
173
|
-
message: `Deleting ${
|
|
292
|
+
message: `Deleting ${filePath} from ${bucketName}`,
|
|
174
293
|
clearOnSuccess: true,
|
|
175
294
|
callback: async () => {
|
|
176
|
-
await s3Client.delete(
|
|
295
|
+
await s3Client.delete(filePath);
|
|
177
296
|
},
|
|
178
297
|
});
|
|
179
298
|
|
|
180
299
|
if (!options.json) {
|
|
181
|
-
tui.success(`Deleted file: ${tui.bold(
|
|
300
|
+
tui.success(`Deleted file: ${tui.bold(filePath)} from ${tui.bold(bucketName)}`);
|
|
182
301
|
}
|
|
183
302
|
|
|
184
303
|
return {
|
|
185
304
|
success: true,
|
|
186
|
-
name:
|
|
305
|
+
name: filePath,
|
|
187
306
|
};
|
|
188
307
|
}
|
|
189
308
|
|