@amodalai/amodal 0.3.89 → 0.3.91
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/CHANGELOG.md +46 -0
- package/dist/src/commands/audit.d.ts +1 -3
- package/dist/src/commands/audit.d.ts.map +1 -1
- package/dist/src/commands/audit.js +4 -53
- package/dist/src/commands/audit.js.map +1 -1
- package/dist/src/commands/build-manifest-types.js +1 -1
- package/dist/src/commands/build-tools.d.ts +3 -10
- package/dist/src/commands/build-tools.d.ts.map +1 -1
- package/dist/src/commands/build-tools.js +5 -118
- package/dist/src/commands/build-tools.js.map +1 -1
- package/dist/src/commands/build.d.ts +23 -5
- package/dist/src/commands/build.d.ts.map +1 -1
- package/dist/src/commands/build.js +53 -34
- package/dist/src/commands/build.js.map +1 -1
- package/dist/src/commands/chat.d.ts +1 -1
- package/dist/src/commands/chat.js +5 -5
- package/dist/src/commands/chat.js.map +1 -1
- package/dist/src/commands/deploy.d.ts +1 -1
- package/dist/src/commands/deploy.d.ts.map +1 -1
- package/dist/src/commands/deploy.js +3 -61
- package/dist/src/commands/deploy.js.map +1 -1
- package/dist/src/commands/deployments.d.ts.map +1 -1
- package/dist/src/commands/deployments.js +3 -36
- package/dist/src/commands/deployments.js.map +1 -1
- package/dist/src/commands/dev.d.ts.map +1 -1
- package/dist/src/commands/dev.js +7 -10
- package/dist/src/commands/dev.js.map +1 -1
- package/dist/src/commands/experiment.d.ts +1 -3
- package/dist/src/commands/experiment.d.ts.map +1 -1
- package/dist/src/commands/experiment.js +4 -102
- package/dist/src/commands/experiment.js.map +1 -1
- package/dist/src/commands/promote.d.ts.map +1 -1
- package/dist/src/commands/promote.js +3 -21
- package/dist/src/commands/promote.js.map +1 -1
- package/dist/src/commands/rollback.d.ts.map +1 -1
- package/dist/src/commands/rollback.js +3 -24
- package/dist/src/commands/rollback.js.map +1 -1
- package/dist/src/commands/secrets.d.ts.map +1 -1
- package/dist/src/commands/secrets.js +2 -102
- package/dist/src/commands/secrets.js.map +1 -1
- package/dist/src/commands/serve.d.ts +2 -11
- package/dist/src/commands/serve.d.ts.map +1 -1
- package/dist/src/commands/serve.js +44 -87
- package/dist/src/commands/serve.js.map +1 -1
- package/dist/src/commands/status.d.ts.map +1 -1
- package/dist/src/commands/status.js +3 -49
- package/dist/src/commands/status.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +7 -8
- package/src/commands/audit.ts +4 -71
- package/src/commands/build-manifest-types.ts +1 -1
- package/src/commands/build-tools.ts +5 -142
- package/src/commands/build.test.ts +14 -9
- package/src/commands/build.ts +73 -33
- package/src/commands/chat.ts +5 -5
- package/src/commands/deploy.test.ts +2 -13
- package/src/commands/deploy.ts +5 -67
- package/src/commands/deployments.ts +3 -39
- package/src/commands/dev.ts +7 -10
- package/src/commands/experiment.ts +4 -110
- package/src/commands/promote.ts +3 -21
- package/src/commands/rollback.ts +3 -24
- package/src/commands/secrets.test.ts +12 -134
- package/src/commands/secrets.ts +2 -116
- package/src/commands/serve.ts +46 -93
- package/src/commands/status.ts +3 -51
- package/src/e2e-commands.test.ts +18 -17
- package/dist/src/shared/platform-client.d.ts +0 -123
- package/dist/src/shared/platform-client.d.ts.map +0 -1
- package/dist/src/shared/platform-client.js +0 -280
- package/dist/src/shared/platform-client.js.map +0 -1
- package/src/commands/audit.test.ts +0 -92
- package/src/commands/experiment.test.ts +0 -125
- package/src/shared/platform-client.test.ts +0 -106
- package/src/shared/platform-client.ts +0 -367
package/src/commands/deploy.ts
CHANGED
|
@@ -6,10 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import type {CommandModule} from 'yargs';
|
|
8
8
|
import {findRepoRoot} from '../shared/repo-discovery.js';
|
|
9
|
-
import {PlatformClient} from '../shared/platform-client.js';
|
|
10
9
|
import {runValidate} from './validate.js';
|
|
11
|
-
import {createRepoTarball} from '../shared/tarball.js';
|
|
12
|
-
import {readProjectLink} from './link.js';
|
|
13
10
|
|
|
14
11
|
export interface DeployOptions {
|
|
15
12
|
cwd?: string;
|
|
@@ -19,7 +16,7 @@ export interface DeployOptions {
|
|
|
19
16
|
}
|
|
20
17
|
|
|
21
18
|
/**
|
|
22
|
-
*
|
|
19
|
+
* Validate a repo before cloud deployment.
|
|
23
20
|
*
|
|
24
21
|
* Returns 0 on success, 1 on error.
|
|
25
22
|
*/
|
|
@@ -48,69 +45,10 @@ export async function runDeploy(options: DeployOptions = {}): Promise<number> {
|
|
|
48
45
|
return 0;
|
|
49
46
|
}
|
|
50
47
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
} catch (err) {
|
|
56
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
57
|
-
process.stderr.write(`[deploy] ${msg}\n`);
|
|
58
|
-
return 1;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const projectLink = await readProjectLink();
|
|
62
|
-
const appId = projectLink?.appId;
|
|
63
|
-
|
|
64
|
-
if (!appId) {
|
|
65
|
-
process.stderr.write('[deploy] No app linked. Run `amodal deploy link` first.\n');
|
|
66
|
-
return 1;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Create tarball
|
|
70
|
-
process.stderr.write('[deploy] Creating tarball...\n');
|
|
71
|
-
const tarballPath = await createRepoTarball(repoPath);
|
|
72
|
-
|
|
73
|
-
try {
|
|
74
|
-
// Trigger remote build
|
|
75
|
-
process.stderr.write('[deploy] Triggering build...\n');
|
|
76
|
-
const buildResult = await client.triggerRemoteBuild(appId, environment, tarballPath, options.message);
|
|
77
|
-
const buildId = buildResult.buildId;
|
|
78
|
-
|
|
79
|
-
process.stderr.write(`[deploy] Build ${buildId} accepted. Waiting for completion...\n`);
|
|
80
|
-
|
|
81
|
-
// Poll for completion
|
|
82
|
-
const maxWaitMs = 5 * 60 * 1000; // 5 minutes
|
|
83
|
-
const pollIntervalMs = 3000;
|
|
84
|
-
const startTime = Date.now();
|
|
85
|
-
|
|
86
|
-
while (Date.now() - startTime < maxWaitMs) {
|
|
87
|
-
await new Promise((r) => setTimeout(r, pollIntervalMs));
|
|
88
|
-
|
|
89
|
-
const status = await client.getBuildStatus(buildId);
|
|
90
|
-
|
|
91
|
-
if (status.status === 'complete') {
|
|
92
|
-
process.stderr.write(`[deploy] Deployed ${status.deployId} to ${status.environment ?? environment}\n`);
|
|
93
|
-
return 0;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (status.status === 'error') {
|
|
97
|
-
process.stderr.write(`[deploy] Build failed: ${status.error ?? 'unknown error'}\n`);
|
|
98
|
-
return 1;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// Still building — continue polling
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
process.stderr.write(`[deploy] Build timed out after 5 minutes. Build ${buildId} may still be running.\n`);
|
|
105
|
-
return 1;
|
|
106
|
-
} catch (err) {
|
|
107
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
108
|
-
process.stderr.write(`[deploy] Deploy failed: ${msg}\n`);
|
|
109
|
-
return 1;
|
|
110
|
-
} finally {
|
|
111
|
-
const {unlinkSync} = await import('node:fs');
|
|
112
|
-
try { unlinkSync(tarballPath); } catch { /* best-effort */ }
|
|
113
|
-
}
|
|
48
|
+
process.stderr.write(
|
|
49
|
+
'[deploy] Cloud deployment is not included in the OSS CLI. Use `amodal deploy build` to create a local snapshot, then deploy it with your hosted control-plane tooling.\n',
|
|
50
|
+
);
|
|
51
|
+
return 1;
|
|
114
52
|
}
|
|
115
53
|
|
|
116
54
|
export const deployCommand: CommandModule = {
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type {CommandModule} from 'yargs';
|
|
8
|
-
import {PlatformClient} from '../shared/platform-client.js';
|
|
9
8
|
|
|
10
9
|
export interface DeploymentsOptions {
|
|
11
10
|
env?: string;
|
|
@@ -17,44 +16,9 @@ export interface DeploymentsOptions {
|
|
|
17
16
|
* List deployment history.
|
|
18
17
|
*/
|
|
19
18
|
export async function runDeployments(options: DeploymentsOptions = {}): Promise<number> {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
} catch (err) {
|
|
24
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
25
|
-
process.stderr.write(`[deployments] ${msg}\n`);
|
|
26
|
-
return 1;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
try {
|
|
30
|
-
const deployments = await client.listDeployments({
|
|
31
|
-
environment: options.env,
|
|
32
|
-
limit: options.limit ?? 10,
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
if (options.json) {
|
|
36
|
-
process.stdout.write(JSON.stringify(deployments, null, 2) + '\n');
|
|
37
|
-
return 0;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
if (deployments.length === 0) {
|
|
41
|
-
process.stderr.write('[deployments] No deployments found.\n');
|
|
42
|
-
return 0;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
for (const d of deployments) {
|
|
46
|
-
const active = d.isActive ? ' [ACTIVE]' : '';
|
|
47
|
-
const msg = d.message ? ` — ${d.message}` : '';
|
|
48
|
-
const sha = d.commitSha ? ` (${d.commitSha.slice(0, 7)})` : '';
|
|
49
|
-
process.stdout.write(`${d.id} ${d.environment}${active} ${d.createdBy ?? d.source}${sha} ${d.createdAt}${msg}\n`);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return 0;
|
|
53
|
-
} catch (err) {
|
|
54
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
55
|
-
process.stderr.write(`[deployments] Failed: ${msg}\n`);
|
|
56
|
-
return 1;
|
|
57
|
-
}
|
|
19
|
+
void options;
|
|
20
|
+
process.stderr.write('[deployments] Hosted deployment history is not included in the OSS CLI.\n');
|
|
21
|
+
return 1;
|
|
58
22
|
}
|
|
59
23
|
|
|
60
24
|
export const deploymentsCommand: CommandModule = {
|
package/src/commands/dev.ts
CHANGED
|
@@ -203,13 +203,11 @@ export function formatLineForDev(line: string): string | null {
|
|
|
203
203
|
return null;
|
|
204
204
|
}
|
|
205
205
|
case 'tool_log': {
|
|
206
|
-
// Tools call ctx.log(...) for noteworthy progress
|
|
207
|
-
// install_template emits "Cloned whodatdev/template-X into
|
|
208
|
-
// agent repo (N connection packages installed)"). When fired
|
|
206
|
+
// Tools call ctx.log(...) for noteworthy progress. When fired
|
|
209
207
|
// during an intent run the callId starts with `intent_`, so
|
|
210
|
-
// these lines pass the quiet filter
|
|
211
|
-
//
|
|
212
|
-
//
|
|
208
|
+
// these lines pass the quiet filter, but as raw JSON they bury
|
|
209
|
+
// the useful message inside callId/session noise. Strip to a
|
|
210
|
+
// clean nested bullet.
|
|
213
211
|
const msg = str('message');
|
|
214
212
|
if (!msg) return null;
|
|
215
213
|
return ` · ${msg}`;
|
|
@@ -753,9 +751,8 @@ Or add it to your agent's .env file:
|
|
|
753
751
|
// Two flows watched by the same poller:
|
|
754
752
|
//
|
|
755
753
|
// 1. AUTO-BOOT — runtime didn't start at CLI launch (no manifest
|
|
756
|
-
// yet). Once amodal.json lands
|
|
757
|
-
//
|
|
758
|
-
// write), boot the runtime in place. Studio's runtime URL
|
|
754
|
+
// yet). Once amodal.json lands, boot the runtime in place.
|
|
755
|
+
// Studio's runtime URL
|
|
759
756
|
// probe picks it up on the next tick.
|
|
760
757
|
//
|
|
761
758
|
// 2. AUTO-RESTART — runtime is already up but amodal.json has
|
|
@@ -766,7 +763,7 @@ Or add it to your agent's .env file:
|
|
|
766
763
|
// bundle in memory and the new packages/config never load.
|
|
767
764
|
//
|
|
768
765
|
// 500ms debounce after detecting a change — lets the writer
|
|
769
|
-
// (commit_setup's atomic rename
|
|
766
|
+
// (commit_setup's atomic rename or a direct file write) settle
|
|
770
767
|
// before loadRepo tries to read.
|
|
771
768
|
// -------------------------------------------------------------------
|
|
772
769
|
|
|
@@ -5,128 +5,22 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type {CommandModule} from 'yargs';
|
|
8
|
-
import {resolvePlatformConfig} from '../shared/platform-client.js';
|
|
9
8
|
|
|
10
9
|
export interface ExperimentOptions {
|
|
11
10
|
action: 'create' | 'deploy' | 'watch' | 'list';
|
|
12
11
|
name?: string;
|
|
13
12
|
id?: string;
|
|
14
|
-
platformUrl?: string;
|
|
15
|
-
platformApiKey?: string;
|
|
16
13
|
controlConfig?: string;
|
|
17
14
|
variantConfig?: string;
|
|
18
15
|
}
|
|
19
16
|
|
|
20
17
|
/**
|
|
21
|
-
*
|
|
18
|
+
* Hosted experiment management is not available from the OSS CLI.
|
|
22
19
|
*/
|
|
23
20
|
export async function runExperimentCommand(options: ExperimentOptions): Promise<void> {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const config = await resolvePlatformConfig({
|
|
28
|
-
url: options.platformUrl,
|
|
29
|
-
apiKey: options.platformApiKey,
|
|
30
|
-
});
|
|
31
|
-
platformUrl = config.url;
|
|
32
|
-
apiKey = config.apiKey;
|
|
33
|
-
} catch (err) {
|
|
34
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
35
|
-
process.stderr.write(`[experiment] ${msg}\n`);
|
|
36
|
-
process.exit(1);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const headers = {
|
|
40
|
-
'Content-Type': 'application/json',
|
|
41
|
-
'Authorization': `Bearer ${apiKey}`,
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
switch (options.action) {
|
|
45
|
-
case 'list': {
|
|
46
|
-
const res = await fetch(`${platformUrl}/api/experiments`, {headers});
|
|
47
|
-
if (!res.ok) {
|
|
48
|
-
process.stderr.write(`[experiment] HTTP ${res.status}\n`);
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- platform response
|
|
52
|
-
const data = await res.json() as {experiments: Array<Record<string, unknown>>};
|
|
53
|
-
if (data.experiments.length === 0) {
|
|
54
|
-
process.stdout.write('No experiments found.\n');
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
for (const exp of data.experiments) {
|
|
58
|
-
process.stdout.write(`${exp['id']} ${exp['name']} [${exp['status']}]\n`);
|
|
59
|
-
}
|
|
60
|
-
break;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
case 'create': {
|
|
64
|
-
if (!options.name) {
|
|
65
|
-
process.stderr.write('[experiment] --name is required for create\n');
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
const controlConfig: Record<string, unknown> = options.controlConfig
|
|
69
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- CLI JSON input
|
|
70
|
-
? JSON.parse(options.controlConfig) as Record<string, unknown>
|
|
71
|
-
: {};
|
|
72
|
-
const variantConfig: Record<string, unknown> = options.variantConfig
|
|
73
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- CLI JSON input
|
|
74
|
-
? JSON.parse(options.variantConfig) as Record<string, unknown>
|
|
75
|
-
: {};
|
|
76
|
-
|
|
77
|
-
const res = await fetch(`${platformUrl}/api/experiments`, {
|
|
78
|
-
method: 'POST',
|
|
79
|
-
headers,
|
|
80
|
-
body: JSON.stringify({name: options.name, controlConfig, variantConfig}),
|
|
81
|
-
});
|
|
82
|
-
if (!res.ok) {
|
|
83
|
-
process.stderr.write(`[experiment] HTTP ${res.status}\n`);
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- platform response
|
|
87
|
-
const {id} = await res.json() as {id: string};
|
|
88
|
-
process.stdout.write(`Created experiment: ${id}\n`);
|
|
89
|
-
break;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
case 'deploy': {
|
|
93
|
-
if (!options.id) {
|
|
94
|
-
process.stderr.write('[experiment] --id is required for deploy\n');
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
const res = await fetch(`${platformUrl}/api/experiments/${options.id}`, {
|
|
98
|
-
method: 'PUT',
|
|
99
|
-
headers,
|
|
100
|
-
body: JSON.stringify({status: 'deployed'}),
|
|
101
|
-
});
|
|
102
|
-
if (!res.ok) {
|
|
103
|
-
process.stderr.write(`[experiment] HTTP ${res.status}\n`);
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
process.stdout.write(`Deployed experiment: ${options.id}\n`);
|
|
107
|
-
break;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
case 'watch': {
|
|
111
|
-
if (!options.id) {
|
|
112
|
-
process.stderr.write('[experiment] --id is required for watch\n');
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
const res = await fetch(`${platformUrl}/api/experiments/${options.id}`, {headers});
|
|
116
|
-
if (!res.ok) {
|
|
117
|
-
process.stderr.write(`[experiment] HTTP ${res.status}\n`);
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- platform response
|
|
121
|
-
const exp = await res.json() as Record<string, unknown>;
|
|
122
|
-
process.stdout.write(JSON.stringify(exp, null, 2) + '\n');
|
|
123
|
-
break;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
default:
|
|
127
|
-
process.stderr.write(`[experiment] Unknown action: ${options.action}\n`);
|
|
128
|
-
break;
|
|
129
|
-
}
|
|
21
|
+
void options;
|
|
22
|
+
process.stderr.write('[experiment] Hosted experiment management is not included in the OSS CLI.\n');
|
|
23
|
+
process.exit(1);
|
|
130
24
|
}
|
|
131
25
|
|
|
132
26
|
export const experimentCommand: CommandModule = {
|
package/src/commands/promote.ts
CHANGED
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type {CommandModule} from 'yargs';
|
|
8
|
-
import {PlatformClient} from '../shared/platform-client.js';
|
|
9
8
|
|
|
10
9
|
export interface PromoteOptions {
|
|
11
10
|
fromEnv: string;
|
|
@@ -16,26 +15,9 @@ export interface PromoteOptions {
|
|
|
16
15
|
* Promote a deployment from one environment to another.
|
|
17
16
|
*/
|
|
18
17
|
export async function runPromote(options: PromoteOptions): Promise<number> {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
} catch (err) {
|
|
23
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
24
|
-
process.stderr.write(`[promote] ${msg}\n`);
|
|
25
|
-
return 1;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const toEnv = options.toEnv ?? 'production';
|
|
29
|
-
|
|
30
|
-
try {
|
|
31
|
-
const result = await client.promote(options.fromEnv, toEnv);
|
|
32
|
-
process.stderr.write(`[promote] Promoted from ${options.fromEnv} to ${toEnv}: ${result.id}\n`);
|
|
33
|
-
return 0;
|
|
34
|
-
} catch (err) {
|
|
35
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
36
|
-
process.stderr.write(`[promote] Failed: ${msg}\n`);
|
|
37
|
-
return 1;
|
|
38
|
-
}
|
|
18
|
+
void options;
|
|
19
|
+
process.stderr.write('[promote] Hosted deployment promotion is not included in the OSS CLI.\n');
|
|
20
|
+
return 1;
|
|
39
21
|
}
|
|
40
22
|
|
|
41
23
|
export const promoteCommand: CommandModule = {
|
package/src/commands/rollback.ts
CHANGED
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type {CommandModule} from 'yargs';
|
|
8
|
-
import {PlatformClient} from '../shared/platform-client.js';
|
|
9
8
|
|
|
10
9
|
export interface RollbackOptions {
|
|
11
10
|
deployId?: string;
|
|
@@ -17,29 +16,9 @@ export interface RollbackOptions {
|
|
|
17
16
|
* Rollback to a previous deployment.
|
|
18
17
|
*/
|
|
19
18
|
export async function runRollback(options: RollbackOptions = {}): Promise<number> {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
} catch (err) {
|
|
24
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
25
|
-
process.stderr.write(`[rollback] ${msg}\n`);
|
|
26
|
-
return 1;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const environment = options.env ?? 'production';
|
|
30
|
-
|
|
31
|
-
try {
|
|
32
|
-
const result = await client.rollback({
|
|
33
|
-
deployId: options.deployId,
|
|
34
|
-
environment,
|
|
35
|
-
});
|
|
36
|
-
process.stderr.write(`[rollback] Rolled back to ${result.id} in ${result.environment}\n`);
|
|
37
|
-
return 0;
|
|
38
|
-
} catch (err) {
|
|
39
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
40
|
-
process.stderr.write(`[rollback] Failed: ${msg}\n`);
|
|
41
|
-
return 1;
|
|
42
|
-
}
|
|
19
|
+
void options;
|
|
20
|
+
process.stderr.write('[rollback] Hosted deployment rollback is not included in the OSS CLI.\n');
|
|
21
|
+
return 1;
|
|
43
22
|
}
|
|
44
23
|
|
|
45
24
|
export const rollbackCommand: CommandModule = {
|
|
@@ -4,22 +4,15 @@
|
|
|
4
4
|
* SPDX-License-Identifier: MIT
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest';
|
|
8
8
|
|
|
9
9
|
describe('runSecrets', () => {
|
|
10
10
|
let stderrOutput: string;
|
|
11
11
|
let stdoutOutput: string;
|
|
12
|
-
|
|
13
|
-
let fetchSpy: any;
|
|
14
|
-
const originalEnv = process.env;
|
|
12
|
+
let fetchSpy: ReturnType<typeof vi.spyOn>;
|
|
15
13
|
|
|
16
14
|
beforeEach(() => {
|
|
17
15
|
vi.clearAllMocks();
|
|
18
|
-
process.env = {
|
|
19
|
-
...originalEnv,
|
|
20
|
-
PLATFORM_API_URL: 'https://api.example.com',
|
|
21
|
-
PLATFORM_API_KEY: 'test-key',
|
|
22
|
-
};
|
|
23
16
|
stderrOutput = '';
|
|
24
17
|
stdoutOutput = '';
|
|
25
18
|
vi.spyOn(process.stderr, 'write').mockImplementation((chunk) => {
|
|
@@ -34,135 +27,20 @@ describe('runSecrets', () => {
|
|
|
34
27
|
});
|
|
35
28
|
|
|
36
29
|
afterEach(() => {
|
|
37
|
-
|
|
38
|
-
fetchSpy.mockRestore();
|
|
30
|
+
vi.restoreAllMocks();
|
|
39
31
|
});
|
|
40
32
|
|
|
41
|
-
it(
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
expect(result).toBe(0);
|
|
47
|
-
expect(stderrOutput).toContain('set successfully');
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it('should return 1 when key missing for set', async () => {
|
|
51
|
-
const {runSecrets} = await import('./secrets.js');
|
|
52
|
-
const result = await runSecrets({subcommand: 'set'});
|
|
53
|
-
expect(result).toBe(1);
|
|
54
|
-
expect(stderrOutput).toContain('Missing key');
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it('should return 1 when value missing for set', async () => {
|
|
58
|
-
const {runSecrets} = await import('./secrets.js');
|
|
59
|
-
const result = await runSecrets({subcommand: 'set', key: 'DB_URL'});
|
|
60
|
-
expect(result).toBe(1);
|
|
61
|
-
expect(stderrOutput).toContain('Missing value');
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it('should list secrets', async () => {
|
|
65
|
-
fetchSpy.mockResolvedValue(new Response(JSON.stringify([
|
|
66
|
-
{key: 'DB_URL'},
|
|
67
|
-
{key: 'API_KEY'},
|
|
68
|
-
]), {status: 200}));
|
|
69
|
-
|
|
70
|
-
const {runSecrets} = await import('./secrets.js');
|
|
71
|
-
const result = await runSecrets({subcommand: 'list'});
|
|
72
|
-
expect(result).toBe(0);
|
|
73
|
-
expect(stdoutOutput).toContain('DB_URL');
|
|
74
|
-
expect(stdoutOutput).toContain('API_KEY');
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it('should list secrets as JSON', async () => {
|
|
78
|
-
fetchSpy.mockResolvedValue(new Response(JSON.stringify([{key: 'DB_URL'}]), {status: 200}));
|
|
79
|
-
|
|
80
|
-
const {runSecrets} = await import('./secrets.js');
|
|
81
|
-
const result = await runSecrets({subcommand: 'list', json: true});
|
|
82
|
-
expect(result).toBe(0);
|
|
83
|
-
const parsed = JSON.parse(stdoutOutput);
|
|
84
|
-
expect(parsed).toHaveLength(1);
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
it('should handle empty secrets list', async () => {
|
|
88
|
-
fetchSpy.mockResolvedValue(new Response('[]', {status: 200}));
|
|
89
|
-
|
|
33
|
+
it.each([
|
|
34
|
+
{subcommand: 'set' as const, key: 'DB_URL', value: 'postgres://example'},
|
|
35
|
+
{subcommand: 'list' as const},
|
|
36
|
+
{subcommand: 'delete' as const, key: 'DB_URL'},
|
|
37
|
+
])('fails closed for hosted secret management in OSS: $subcommand', async (options) => {
|
|
90
38
|
const {runSecrets} = await import('./secrets.js');
|
|
91
|
-
const result = await runSecrets(
|
|
92
|
-
expect(result).toBe(0);
|
|
93
|
-
expect(stderrOutput).toContain('No secrets configured');
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
it('should delete a secret', async () => {
|
|
97
|
-
fetchSpy.mockResolvedValue(new Response('{}', {status: 200}));
|
|
39
|
+
const result = await runSecrets(options);
|
|
98
40
|
|
|
99
|
-
const {runSecrets} = await import('./secrets.js');
|
|
100
|
-
const result = await runSecrets({subcommand: 'delete', key: 'DB_URL'});
|
|
101
|
-
expect(result).toBe(0);
|
|
102
|
-
expect(stderrOutput).toContain('deleted');
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
it('should return 1 when key missing for delete', async () => {
|
|
106
|
-
const {runSecrets} = await import('./secrets.js');
|
|
107
|
-
const result = await runSecrets({subcommand: 'delete'});
|
|
108
41
|
expect(result).toBe(1);
|
|
109
|
-
expect(stderrOutput).toContain('
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
it('should return 1 when platform not configured', async () => {
|
|
113
|
-
delete process.env['PLATFORM_API_URL'];
|
|
114
|
-
delete process.env['PLATFORM_API_KEY'];
|
|
115
|
-
|
|
116
|
-
const {runSecrets} = await import('./secrets.js');
|
|
117
|
-
const result = await runSecrets({subcommand: 'list'});
|
|
118
|
-
expect(result).toBe(1);
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
it('should handle auth failure', async () => {
|
|
122
|
-
fetchSpy.mockResolvedValue(new Response('Unauthorized', {status: 401}));
|
|
123
|
-
|
|
124
|
-
const {runSecrets} = await import('./secrets.js');
|
|
125
|
-
const result = await runSecrets({subcommand: 'list'});
|
|
126
|
-
expect(result).toBe(1);
|
|
127
|
-
expect(stderrOutput).toContain('401');
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
it('should handle network error', async () => {
|
|
131
|
-
fetchSpy.mockRejectedValue(new Error('ECONNREFUSED'));
|
|
132
|
-
|
|
133
|
-
const {runSecrets} = await import('./secrets.js');
|
|
134
|
-
const result = await runSecrets({subcommand: 'set', key: 'K', value: 'V'});
|
|
135
|
-
expect(result).toBe(1);
|
|
136
|
-
expect(stderrOutput).toContain('ECONNREFUSED');
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
it('should handle set server error', async () => {
|
|
140
|
-
fetchSpy.mockResolvedValue(new Response('Internal Error', {status: 500}));
|
|
141
|
-
|
|
142
|
-
const {runSecrets} = await import('./secrets.js');
|
|
143
|
-
const result = await runSecrets({subcommand: 'set', key: 'K', value: 'V'});
|
|
144
|
-
expect(result).toBe(1);
|
|
145
|
-
expect(stderrOutput).toContain('500');
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
it('should show count for listed secrets', async () => {
|
|
149
|
-
fetchSpy.mockResolvedValue(new Response(JSON.stringify([
|
|
150
|
-
{key: 'A'},
|
|
151
|
-
{key: 'B'},
|
|
152
|
-
{key: 'C'},
|
|
153
|
-
]), {status: 200}));
|
|
154
|
-
|
|
155
|
-
const {runSecrets} = await import('./secrets.js');
|
|
156
|
-
await runSecrets({subcommand: 'list'});
|
|
157
|
-
expect(stderrOutput).toContain('3 secrets');
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
it('should use singular for single secret', async () => {
|
|
161
|
-
fetchSpy.mockResolvedValue(new Response(JSON.stringify([{key: 'ONLY'}]), {status: 200}));
|
|
162
|
-
|
|
163
|
-
const {runSecrets} = await import('./secrets.js');
|
|
164
|
-
await runSecrets({subcommand: 'list'});
|
|
165
|
-
expect(stderrOutput).toContain('1 secret configured');
|
|
166
|
-
expect(stderrOutput).not.toContain('1 secrets');
|
|
42
|
+
expect(stderrOutput).toContain('Hosted secret management is not included in the OSS CLI');
|
|
43
|
+
expect(stdoutOutput).toBe('');
|
|
44
|
+
expect(fetchSpy).not.toHaveBeenCalled();
|
|
167
45
|
});
|
|
168
46
|
});
|