@laioutr/cli 0.3.2 → 0.4.1
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 +17 -0
- package/README.md +168 -25
- package/dist/api/deploy.d.ts +66 -0
- package/dist/api/deploy.js +34 -0
- package/dist/api/fetchRc.d.ts +1 -0
- package/dist/api/fetchRc.js +12 -28
- package/dist/api/http.d.ts +30 -0
- package/dist/api/http.js +93 -0
- package/dist/api/releaseAppVersion.d.ts +1 -0
- package/dist/api/releaseAppVersion.js +9 -20
- package/dist/commands/app/release.d.ts +2 -1
- package/dist/commands/app/release.js +2 -1
- package/dist/commands/deploy/list.d.ts +32 -0
- package/dist/commands/deploy/list.js +55 -0
- package/dist/commands/deploy/logs.d.ts +23 -0
- package/dist/commands/deploy/logs.js +54 -0
- package/dist/commands/deploy/status.d.ts +31 -0
- package/dist/commands/deploy/status.js +41 -0
- package/dist/commands/deploy/trigger.d.ts +24 -0
- package/dist/commands/deploy/trigger.js +130 -0
- package/dist/commands/rc/fetch.d.ts +4 -3
- package/dist/commands/rc/fetch.js +24 -3
- package/dist/commands/rc/update.d.ts +2 -1
- package/dist/commands/rc/update.js +2 -1
- package/dist/deploy/attribution.d.ts +12 -0
- package/dist/deploy/attribution.js +16 -0
- package/dist/deploy/log-renderer.d.ts +13 -0
- package/dist/deploy/log-renderer.js +21 -0
- package/dist/deploy/polling.d.ts +4 -0
- package/dist/deploy/polling.js +12 -0
- package/dist/deploy/validation.d.ts +81 -0
- package/dist/deploy/validation.js +47 -0
- package/dist/deploy-base-command.d.ts +12 -0
- package/dist/deploy-base-command.js +27 -0
- package/dist/format/color.d.ts +5 -0
- package/dist/format/color.js +10 -0
- package/dist/format/detail.d.ts +19 -0
- package/dist/format/detail.js +18 -0
- package/dist/laioutr-base-command.d.ts +9 -1
- package/dist/laioutr-base-command.js +21 -3
- package/dist/project.d.ts +14 -0
- package/dist/project.js +16 -0
- package/oclif.manifest.json +443 -22
- package/package.json +4 -2
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import DeployBaseCommand from '../../deploy-base-command.js';
|
|
2
|
+
export default class DeployList extends DeployBaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static enableJsonFlag: boolean;
|
|
5
|
+
static examples: string[];
|
|
6
|
+
static flags: {
|
|
7
|
+
limit: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
project: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
key: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
'no-color': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
|
+
'cockpit-api-host': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
cwd: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
};
|
|
14
|
+
run(): Promise<{
|
|
15
|
+
deployments: {
|
|
16
|
+
deploymentId: string;
|
|
17
|
+
status: string;
|
|
18
|
+
environment: string;
|
|
19
|
+
previewName: string | null;
|
|
20
|
+
url: string | null;
|
|
21
|
+
createdAt: string;
|
|
22
|
+
stoppedAt: string | null;
|
|
23
|
+
attribution: {
|
|
24
|
+
principalType: "user" | "api-key" | "system";
|
|
25
|
+
displayName: string;
|
|
26
|
+
email: string | null;
|
|
27
|
+
agentName: string | null;
|
|
28
|
+
redacted: boolean;
|
|
29
|
+
} | null;
|
|
30
|
+
}[];
|
|
31
|
+
}>;
|
|
32
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
2
|
+
import { makeTable } from '@oclif/table';
|
|
3
|
+
import { fetchDeployments } from '../../api/deploy.js';
|
|
4
|
+
import { formatAttribution } from '../../deploy/attribution.js';
|
|
5
|
+
import DeployBaseCommand from '../../deploy-base-command.js';
|
|
6
|
+
export default class DeployList extends DeployBaseCommand {
|
|
7
|
+
static description = 'List recent deployments for a project.';
|
|
8
|
+
static enableJsonFlag = true;
|
|
9
|
+
static examples = [
|
|
10
|
+
'<%= config.bin %> deploy list --project org/project --key orgKey_xxx',
|
|
11
|
+
'<%= config.bin %> deploy list --project org/project --key orgKey_xxx --limit 50',
|
|
12
|
+
];
|
|
13
|
+
static flags = {
|
|
14
|
+
...DeployBaseCommand.flags,
|
|
15
|
+
limit: Flags.integer({
|
|
16
|
+
default: 20,
|
|
17
|
+
description: 'Maximum number of deployments to list (1-100)',
|
|
18
|
+
max: 100,
|
|
19
|
+
min: 1,
|
|
20
|
+
}),
|
|
21
|
+
};
|
|
22
|
+
async run() {
|
|
23
|
+
const { flags } = await this.parse(DeployList);
|
|
24
|
+
const { organizationSlug, projectSlug } = this.parseProject(flags.project);
|
|
25
|
+
const result = await fetchDeployments({
|
|
26
|
+
cockpitApiHost: flags['cockpit-api-host'],
|
|
27
|
+
apiKey: flags.key,
|
|
28
|
+
organizationSlug,
|
|
29
|
+
projectSlug,
|
|
30
|
+
userAgent: this.config.userAgent,
|
|
31
|
+
limit: flags.limit,
|
|
32
|
+
});
|
|
33
|
+
if (result.deployments.length === 0) {
|
|
34
|
+
this.log('No deployments found.');
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
const rows = result.deployments.map((d) => ({
|
|
38
|
+
ID: d.deploymentId,
|
|
39
|
+
Type: d.environment,
|
|
40
|
+
Preview: d.previewName ?? '—',
|
|
41
|
+
Status: d.status,
|
|
42
|
+
URL: d.url ?? '—',
|
|
43
|
+
'Created by': formatAttribution(d.attribution),
|
|
44
|
+
}));
|
|
45
|
+
this.log(makeTable({
|
|
46
|
+
data: rows,
|
|
47
|
+
// Keep our exact header casing (ID / URL / Created by) instead of the
|
|
48
|
+
// default case-formatter, and truncate the middle of long cells (URLs)
|
|
49
|
+
// so both ends stay visible.
|
|
50
|
+
headerOptions: { formatter: (header) => header },
|
|
51
|
+
overflow: 'truncate-middle',
|
|
52
|
+
}));
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import DeployBaseCommand from '../../deploy-base-command.js';
|
|
2
|
+
export default class DeployLogs extends DeployBaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static enableJsonFlag: boolean;
|
|
5
|
+
static examples: string[];
|
|
6
|
+
static flags: {
|
|
7
|
+
'deployment-id': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
project: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
key: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
'no-color': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
|
+
'cockpit-api-host': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
cwd: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
};
|
|
14
|
+
run(): Promise<{
|
|
15
|
+
lines: {
|
|
16
|
+
text: string;
|
|
17
|
+
level?: "error" | "warning" | null | undefined;
|
|
18
|
+
timestamp?: number | null | undefined;
|
|
19
|
+
}[] | null;
|
|
20
|
+
supported: boolean;
|
|
21
|
+
deploymentStatus: string;
|
|
22
|
+
}>;
|
|
23
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
2
|
+
import { fetchDeploymentLogs } from '../../api/deploy.js';
|
|
3
|
+
import { LogRenderer } from '../../deploy/log-renderer.js';
|
|
4
|
+
import { isTerminalStatus, POLL_INTERVAL_MS, sleep } from '../../deploy/polling.js';
|
|
5
|
+
import DeployBaseCommand from '../../deploy-base-command.js';
|
|
6
|
+
export default class DeployLogs extends DeployBaseCommand {
|
|
7
|
+
static description = 'Fetch and display build logs for a deployment.';
|
|
8
|
+
static enableJsonFlag = true;
|
|
9
|
+
static examples = ['<%= config.bin %> deploy logs --project org/project --key orgKey_xxx --deployment-id dep_abc123'];
|
|
10
|
+
static flags = {
|
|
11
|
+
...DeployBaseCommand.flags,
|
|
12
|
+
'deployment-id': Flags.string({
|
|
13
|
+
aliases: ['deploymentId'],
|
|
14
|
+
deprecateAliases: true,
|
|
15
|
+
description: 'Deployment ID',
|
|
16
|
+
required: true,
|
|
17
|
+
}),
|
|
18
|
+
};
|
|
19
|
+
async run() {
|
|
20
|
+
const { flags } = await this.parse(DeployLogs);
|
|
21
|
+
const { organizationSlug, projectSlug } = this.parseProject(flags.project);
|
|
22
|
+
const apiOptions = {
|
|
23
|
+
cockpitApiHost: flags['cockpit-api-host'],
|
|
24
|
+
apiKey: flags.key,
|
|
25
|
+
organizationSlug,
|
|
26
|
+
projectSlug,
|
|
27
|
+
userAgent: this.config.userAgent,
|
|
28
|
+
deploymentId: flags['deployment-id'],
|
|
29
|
+
};
|
|
30
|
+
// Initial fetch
|
|
31
|
+
const initial = await fetchDeploymentLogs(apiOptions);
|
|
32
|
+
if (!initial.supported) {
|
|
33
|
+
this.log("Build logs are not available for this deployment's hosting provider.");
|
|
34
|
+
return initial;
|
|
35
|
+
}
|
|
36
|
+
const isFollowing = !isTerminalStatus(initial.deploymentStatus);
|
|
37
|
+
this.log(`Build log for deployment ${flags['deployment-id']}${isFollowing ? ' (following)' : ''}`);
|
|
38
|
+
this.log('');
|
|
39
|
+
const renderer = new LogRenderer();
|
|
40
|
+
renderer.render(initial.lines, (msg) => this.log(msg));
|
|
41
|
+
if (!isFollowing) {
|
|
42
|
+
return initial;
|
|
43
|
+
}
|
|
44
|
+
// Follow mode — poll until terminal
|
|
45
|
+
while (true) {
|
|
46
|
+
await sleep(POLL_INTERVAL_MS);
|
|
47
|
+
const result = await fetchDeploymentLogs(apiOptions);
|
|
48
|
+
renderer.render(result.lines, (msg) => this.log(msg));
|
|
49
|
+
if (isTerminalStatus(result.deploymentStatus)) {
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import DeployBaseCommand from '../../deploy-base-command.js';
|
|
2
|
+
export default class DeployStatus extends DeployBaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static enableJsonFlag: boolean;
|
|
5
|
+
static examples: string[];
|
|
6
|
+
static flags: {
|
|
7
|
+
'deployment-id': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
project: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
key: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
'no-color': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
|
+
'cockpit-api-host': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
cwd: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
};
|
|
14
|
+
run(): Promise<{
|
|
15
|
+
deploymentId: string;
|
|
16
|
+
status: string;
|
|
17
|
+
environment: string;
|
|
18
|
+
previewName: string | null;
|
|
19
|
+
url: string | null;
|
|
20
|
+
lastError: string | null;
|
|
21
|
+
createdAt: string;
|
|
22
|
+
stoppedAt: string | null;
|
|
23
|
+
attribution: {
|
|
24
|
+
principalType: "user" | "api-key" | "system";
|
|
25
|
+
displayName: string;
|
|
26
|
+
email: string | null;
|
|
27
|
+
agentName: string | null;
|
|
28
|
+
redacted: boolean;
|
|
29
|
+
} | null;
|
|
30
|
+
}>;
|
|
31
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
2
|
+
import { fetchDeploymentStatus } from '../../api/deploy.js';
|
|
3
|
+
import { formatAttribution } from '../../deploy/attribution.js';
|
|
4
|
+
import DeployBaseCommand from '../../deploy-base-command.js';
|
|
5
|
+
import { formatDetail } from '../../format/detail.js';
|
|
6
|
+
export default class DeployStatus extends DeployBaseCommand {
|
|
7
|
+
static description = 'Show status of a deployment.';
|
|
8
|
+
static enableJsonFlag = true;
|
|
9
|
+
static examples = ['<%= config.bin %> deploy status --project org/project --key orgKey_xxx --deployment-id dep_abc123'];
|
|
10
|
+
static flags = {
|
|
11
|
+
...DeployBaseCommand.flags,
|
|
12
|
+
'deployment-id': Flags.string({
|
|
13
|
+
aliases: ['deploymentId'],
|
|
14
|
+
deprecateAliases: true,
|
|
15
|
+
description: 'Deployment ID',
|
|
16
|
+
required: true,
|
|
17
|
+
}),
|
|
18
|
+
};
|
|
19
|
+
async run() {
|
|
20
|
+
const { flags } = await this.parse(DeployStatus);
|
|
21
|
+
const { organizationSlug, projectSlug } = this.parseProject(flags.project);
|
|
22
|
+
const status = await fetchDeploymentStatus({
|
|
23
|
+
cockpitApiHost: flags['cockpit-api-host'],
|
|
24
|
+
apiKey: flags.key,
|
|
25
|
+
organizationSlug,
|
|
26
|
+
projectSlug,
|
|
27
|
+
userAgent: this.config.userAgent,
|
|
28
|
+
deploymentId: flags['deployment-id'],
|
|
29
|
+
});
|
|
30
|
+
this.log(formatDetail([
|
|
31
|
+
['Status', status.status],
|
|
32
|
+
['Environment', status.environment],
|
|
33
|
+
['Preview', status.previewName],
|
|
34
|
+
['URL', status.url],
|
|
35
|
+
['Created by', formatAttribution(status.attribution)],
|
|
36
|
+
['Created at', status.createdAt],
|
|
37
|
+
['Stopped at', status.stoppedAt],
|
|
38
|
+
], { title: `Deployment ${status.deploymentId}` }));
|
|
39
|
+
return status;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import DeployBaseCommand from '../../deploy-base-command.js';
|
|
2
|
+
export default class DeployTrigger extends DeployBaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static enableJsonFlag: boolean;
|
|
5
|
+
static examples: string[];
|
|
6
|
+
static flags: {
|
|
7
|
+
wait: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
8
|
+
logs: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
9
|
+
preview: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
project: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
key: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
'no-color': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
13
|
+
'cockpit-api-host': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
|
+
cwd: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
|
+
};
|
|
16
|
+
run(): Promise<{
|
|
17
|
+
deploymentId: string;
|
|
18
|
+
status: string;
|
|
19
|
+
environment: string;
|
|
20
|
+
previewName: string | null;
|
|
21
|
+
}>;
|
|
22
|
+
private pollWithSpinner;
|
|
23
|
+
private pollWithLogs;
|
|
24
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { Flags, ux } from '@oclif/core';
|
|
2
|
+
import { fetchDeploymentLogs, fetchDeploymentStatus, triggerDeployment } from '../../api/deploy.js';
|
|
3
|
+
import { LogRenderer } from '../../deploy/log-renderer.js';
|
|
4
|
+
import { isTerminalStatus, POLL_INTERVAL_MS, sleep } from '../../deploy/polling.js';
|
|
5
|
+
import DeployBaseCommand from '../../deploy-base-command.js';
|
|
6
|
+
export default class DeployTrigger extends DeployBaseCommand {
|
|
7
|
+
static description = 'Trigger a project deployment.';
|
|
8
|
+
static enableJsonFlag = true;
|
|
9
|
+
static examples = [
|
|
10
|
+
'<%= config.bin %> deploy trigger --project org/project --key orgKey_xxx',
|
|
11
|
+
'<%= config.bin %> deploy trigger --project org/project --key orgKey_xxx --no-wait',
|
|
12
|
+
'<%= config.bin %> deploy trigger --project org/project --key orgKey_xxx --logs',
|
|
13
|
+
'<%= config.bin %> deploy trigger --project org/project --key orgKey_xxx --preview my-feature',
|
|
14
|
+
];
|
|
15
|
+
static flags = {
|
|
16
|
+
...DeployBaseCommand.flags,
|
|
17
|
+
wait: Flags.boolean({
|
|
18
|
+
allowNo: true,
|
|
19
|
+
default: true,
|
|
20
|
+
description: 'Wait for deployment to complete',
|
|
21
|
+
}),
|
|
22
|
+
logs: Flags.boolean({
|
|
23
|
+
default: false,
|
|
24
|
+
description: 'Stream build log output instead of spinner',
|
|
25
|
+
}),
|
|
26
|
+
preview: Flags.string({
|
|
27
|
+
description: 'Create a preview deployment with optional name',
|
|
28
|
+
}),
|
|
29
|
+
};
|
|
30
|
+
async run() {
|
|
31
|
+
const { flags } = await this.parse(DeployTrigger);
|
|
32
|
+
const { organizationSlug, projectSlug } = this.parseProject(flags.project);
|
|
33
|
+
const apiOptions = {
|
|
34
|
+
cockpitApiHost: flags['cockpit-api-host'],
|
|
35
|
+
apiKey: flags.key,
|
|
36
|
+
organizationSlug,
|
|
37
|
+
projectSlug,
|
|
38
|
+
userAgent: this.config.userAgent,
|
|
39
|
+
};
|
|
40
|
+
// A named preview requires a non-empty name — the deploy route rejects an
|
|
41
|
+
// empty `previewName`, and an unnamed preview is not supported by this contract.
|
|
42
|
+
if (flags.preview !== undefined && flags.preview.trim() === '') {
|
|
43
|
+
this.error('--preview requires a non-empty name');
|
|
44
|
+
}
|
|
45
|
+
const isPreview = flags.preview !== undefined;
|
|
46
|
+
const label = isPreview ? `preview deployment for ${flags.project} (${flags.preview})` : `deployment for ${flags.project}`;
|
|
47
|
+
this.log(`Triggering ${label}...`);
|
|
48
|
+
// Trigger
|
|
49
|
+
const result = await triggerDeployment({
|
|
50
|
+
...apiOptions,
|
|
51
|
+
previewName: flags.preview,
|
|
52
|
+
});
|
|
53
|
+
this.log(`Deployment ID: ${result.deploymentId}`);
|
|
54
|
+
this.log('');
|
|
55
|
+
if (!flags.wait) {
|
|
56
|
+
this.log(`Status: ${result.status}`);
|
|
57
|
+
this.log('');
|
|
58
|
+
this.log('Deployment triggered. Use `laioutr deploy status` to check progress.');
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
// Poll until terminal
|
|
62
|
+
if (flags.logs) {
|
|
63
|
+
return this.pollWithLogs(apiOptions, result.deploymentId);
|
|
64
|
+
}
|
|
65
|
+
return this.pollWithSpinner(apiOptions, result.deploymentId);
|
|
66
|
+
}
|
|
67
|
+
async pollWithSpinner(apiOptions, deploymentId) {
|
|
68
|
+
let lastStatus = '';
|
|
69
|
+
while (true) {
|
|
70
|
+
const status = await fetchDeploymentStatus({ ...apiOptions, deploymentId });
|
|
71
|
+
if (status.status !== lastStatus) {
|
|
72
|
+
if (lastStatus)
|
|
73
|
+
ux.action.stop();
|
|
74
|
+
if (!isTerminalStatus(status.status)) {
|
|
75
|
+
ux.action.start(`Status: ${status.status}`);
|
|
76
|
+
}
|
|
77
|
+
lastStatus = status.status;
|
|
78
|
+
}
|
|
79
|
+
if (isTerminalStatus(status.status)) {
|
|
80
|
+
ux.action.stop();
|
|
81
|
+
this.log('');
|
|
82
|
+
// `not_available` is a success outcome: the provider does not report
|
|
83
|
+
// live status, so the deployment is promoted straight to live. Only
|
|
84
|
+
// `error`/`canceled` (and any unexpected terminal status) are failures.
|
|
85
|
+
if (status.status === 'success' || status.status === 'not_available') {
|
|
86
|
+
this.log(status.status === 'success' ? '✓ Deployment successful' : '✓ Deployment live (provider does not report build status)');
|
|
87
|
+
if (status.url)
|
|
88
|
+
this.log(` URL: ${status.url}`);
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
this.log(`✗ Deployment ${status.status}`);
|
|
92
|
+
if (status.lastError)
|
|
93
|
+
this.log(` Error: ${status.lastError}`);
|
|
94
|
+
this.exit(1);
|
|
95
|
+
}
|
|
96
|
+
return status;
|
|
97
|
+
}
|
|
98
|
+
await sleep(POLL_INTERVAL_MS);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
async pollWithLogs(apiOptions, deploymentId) {
|
|
102
|
+
const renderer = new LogRenderer();
|
|
103
|
+
let lastStatus = '';
|
|
104
|
+
while (true) {
|
|
105
|
+
const logsResult = await fetchDeploymentLogs({ ...apiOptions, deploymentId });
|
|
106
|
+
if (logsResult.deploymentStatus !== lastStatus) {
|
|
107
|
+
this.log(`── Status: ${logsResult.deploymentStatus} ──`);
|
|
108
|
+
lastStatus = logsResult.deploymentStatus;
|
|
109
|
+
}
|
|
110
|
+
renderer.render(logsResult.lines, (msg) => this.log(msg));
|
|
111
|
+
if (isTerminalStatus(logsResult.deploymentStatus)) {
|
|
112
|
+
this.log('');
|
|
113
|
+
// `success` and `not_available` are both live outcomes (see pollWithSpinner);
|
|
114
|
+
// fetch the final status for the URL. Everything else is a failure.
|
|
115
|
+
if (logsResult.deploymentStatus === 'success' || logsResult.deploymentStatus === 'not_available') {
|
|
116
|
+
const finalStatus = await fetchDeploymentStatus({ ...apiOptions, deploymentId });
|
|
117
|
+
this.log(logsResult.deploymentStatus === 'success' ?
|
|
118
|
+
'✓ Deployment successful'
|
|
119
|
+
: '✓ Deployment live (provider does not report build status)');
|
|
120
|
+
if (finalStatus.url)
|
|
121
|
+
this.log(` URL: ${finalStatus.url}`);
|
|
122
|
+
return finalStatus;
|
|
123
|
+
}
|
|
124
|
+
this.log(`✗ Deployment ${logsResult.deploymentStatus}`);
|
|
125
|
+
this.exit(1);
|
|
126
|
+
}
|
|
127
|
+
await sleep(POLL_INTERVAL_MS);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -6,10 +6,11 @@ export default class ProjectRcFetch extends LaioutrBaseCommand {
|
|
|
6
6
|
static description: string;
|
|
7
7
|
static examples: string[];
|
|
8
8
|
static flags: {
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
'environment-name': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
'project-secret': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
11
|
project: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
-
|
|
12
|
+
'no-color': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
13
|
+
'cockpit-api-host': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
14
|
cwd: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
15
|
};
|
|
15
16
|
static aliases: string[];
|
|
@@ -12,15 +12,36 @@ export default class ProjectRcFetch extends LaioutrBaseCommand {
|
|
|
12
12
|
static examples = ['<%= config.bin %> <%= command.id %>'];
|
|
13
13
|
static flags = {
|
|
14
14
|
...LaioutrBaseCommand.flags,
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
'environment-name': Flags.string({
|
|
16
|
+
aliases: ['environmentName'],
|
|
17
|
+
char: 'e',
|
|
18
|
+
default: 'main',
|
|
19
|
+
deprecateAliases: true,
|
|
20
|
+
description: 'Environment name',
|
|
21
|
+
env: 'ENVIRONMENT_NAME',
|
|
22
|
+
required: true,
|
|
23
|
+
}),
|
|
24
|
+
'project-secret': Flags.string({
|
|
25
|
+
aliases: ['projectSecret'],
|
|
26
|
+
char: 's',
|
|
27
|
+
deprecateAliases: true,
|
|
28
|
+
description: 'Project secret',
|
|
29
|
+
env: 'PROJECT_SECRET',
|
|
30
|
+
required: true,
|
|
31
|
+
}),
|
|
17
32
|
project: Flags.string({ char: 'p', description: '<organization slug>/<project slug>', env: 'PROJECT', required: true }),
|
|
18
33
|
};
|
|
19
34
|
static aliases = ['project:fetch-rc'];
|
|
20
35
|
static deprecateAliases = true;
|
|
21
36
|
async run() {
|
|
22
37
|
const { args, flags } = await this.parse(ProjectRcFetch);
|
|
23
|
-
const rc = await fetchRc(
|
|
38
|
+
const rc = await fetchRc({
|
|
39
|
+
cockpitApiHost: flags['cockpit-api-host'],
|
|
40
|
+
environmentName: flags['environment-name'],
|
|
41
|
+
project: flags.project,
|
|
42
|
+
projectSecret: flags['project-secret'],
|
|
43
|
+
userAgent: this.config.userAgent,
|
|
44
|
+
});
|
|
24
45
|
const filePath = path.join(flags.cwd, args.fileName);
|
|
25
46
|
this.log('Writing RC file', filePath);
|
|
26
47
|
return writeFile(filePath, rc);
|
|
@@ -6,7 +6,8 @@ export default class ProjectRcUpdate extends LaioutrBaseCommand {
|
|
|
6
6
|
static description: string;
|
|
7
7
|
static examples: string[];
|
|
8
8
|
static flags: {
|
|
9
|
-
|
|
9
|
+
'no-color': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
|
+
'cockpit-api-host': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
11
|
cwd: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
12
|
};
|
|
12
13
|
run(): Promise<void>;
|
|
@@ -19,10 +19,11 @@ export default class ProjectRcUpdate extends LaioutrBaseCommand {
|
|
|
19
19
|
const currentRcRaw = await readFile(path.join(flags.cwd, args.fileName), 'utf-8');
|
|
20
20
|
const currentRc = rcWithLaioutrMetaSchema.parse(JSON.parse(currentRcRaw));
|
|
21
21
|
const options = {
|
|
22
|
-
|
|
22
|
+
cockpitApiHost: flags['cockpit-api-host'],
|
|
23
23
|
project: currentRc.laioutr.projectSlug,
|
|
24
24
|
projectSecret: currentRc.laioutr.projectSecretKey,
|
|
25
25
|
environmentName: currentRc.laioutr.environmentName,
|
|
26
|
+
userAgent: this.config.userAgent,
|
|
26
27
|
};
|
|
27
28
|
this.log('Fetching latest RC for project', options.project);
|
|
28
29
|
const rc = await fetchRc(options);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { attributionSchema } from './validation.js';
|
|
2
|
+
import type { z } from 'zod/v4';
|
|
3
|
+
type Attribution = z.infer<typeof attributionSchema>;
|
|
4
|
+
/**
|
|
5
|
+
* Renders the deploy read-path "Created by" prose. This display string is
|
|
6
|
+
* derived here, never stored server-side.
|
|
7
|
+
*
|
|
8
|
+
* `redacted` wins over `agentName`: a redacted row's `displayName` is scrubbed
|
|
9
|
+
* server-side, so it must never be rendered — not even with an agent suffix.
|
|
10
|
+
*/
|
|
11
|
+
export declare function formatAttribution(attribution: Attribution | null): string;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Renders the deploy read-path "Created by" prose. This display string is
|
|
3
|
+
* derived here, never stored server-side.
|
|
4
|
+
*
|
|
5
|
+
* `redacted` wins over `agentName`: a redacted row's `displayName` is scrubbed
|
|
6
|
+
* server-side, so it must never be rendered — not even with an agent suffix.
|
|
7
|
+
*/
|
|
8
|
+
export function formatAttribution(attribution) {
|
|
9
|
+
if (!attribution)
|
|
10
|
+
return 'Unknown';
|
|
11
|
+
if (attribution.redacted)
|
|
12
|
+
return 'Deleted user';
|
|
13
|
+
if (attribution.agentName)
|
|
14
|
+
return `${attribution.displayName} (via ${attribution.agentName})`;
|
|
15
|
+
return attribution.displayName;
|
|
16
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { buildLogLineSchema } from './validation.js';
|
|
2
|
+
import type { z } from 'zod/v4';
|
|
3
|
+
type BuildLogLine = z.infer<typeof buildLogLineSchema>;
|
|
4
|
+
export declare class LogRenderer {
|
|
5
|
+
private lastIndex;
|
|
6
|
+
/**
|
|
7
|
+
* Renders new log lines since the last call.
|
|
8
|
+
* Returns the number of new lines rendered.
|
|
9
|
+
*/
|
|
10
|
+
render(lines: BuildLogLine[] | null, log: (msg: string) => void): number;
|
|
11
|
+
reset(): void;
|
|
12
|
+
}
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { stripColorsIfDisabled } from '../format/color.js';
|
|
2
|
+
export class LogRenderer {
|
|
3
|
+
lastIndex = 0;
|
|
4
|
+
/**
|
|
5
|
+
* Renders new log lines since the last call.
|
|
6
|
+
* Returns the number of new lines rendered.
|
|
7
|
+
*/
|
|
8
|
+
render(lines, log) {
|
|
9
|
+
if (!lines)
|
|
10
|
+
return 0;
|
|
11
|
+
const newLines = lines.slice(this.lastIndex);
|
|
12
|
+
for (const line of newLines) {
|
|
13
|
+
log(stripColorsIfDisabled(line.text));
|
|
14
|
+
}
|
|
15
|
+
this.lastIndex = lines.length;
|
|
16
|
+
return newLines.length;
|
|
17
|
+
}
|
|
18
|
+
reset() {
|
|
19
|
+
this.lastIndex = 0;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const POLL_INTERVAL_MS = 5000;
|
|
2
|
+
// Mirrors the terminal members of the DB `project_deployment_status` enum.
|
|
3
|
+
// `not_available` is terminal: the hosting facade sets it (and promotes) when a
|
|
4
|
+
// provider lacks live status updates, so a poll loop must treat it as final.
|
|
5
|
+
const TERMINAL_STATUSES = new Set(['success', 'error', 'canceled', 'not_available']);
|
|
6
|
+
export function isTerminalStatus(status) {
|
|
7
|
+
return TERMINAL_STATUSES.has(status);
|
|
8
|
+
}
|
|
9
|
+
export function sleep(ms) {
|
|
10
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
11
|
+
}
|
|
12
|
+
export { POLL_INTERVAL_MS };
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { z } from 'zod/v4';
|
|
2
|
+
export declare const deployTriggerResponseSchema: z.ZodObject<{
|
|
3
|
+
deploymentId: z.ZodString;
|
|
4
|
+
status: z.ZodString;
|
|
5
|
+
environment: z.ZodString;
|
|
6
|
+
previewName: z.ZodNullable<z.ZodString>;
|
|
7
|
+
}, z.core.$strip>;
|
|
8
|
+
export declare const attributionSchema: z.ZodObject<{
|
|
9
|
+
principalType: z.ZodEnum<{
|
|
10
|
+
user: "user";
|
|
11
|
+
"api-key": "api-key";
|
|
12
|
+
system: "system";
|
|
13
|
+
}>;
|
|
14
|
+
displayName: z.ZodString;
|
|
15
|
+
email: z.ZodNullable<z.ZodString>;
|
|
16
|
+
agentName: z.ZodNullable<z.ZodString>;
|
|
17
|
+
redacted: z.ZodBoolean;
|
|
18
|
+
}, z.core.$strip>;
|
|
19
|
+
export declare const deploymentStatusResponseSchema: z.ZodObject<{
|
|
20
|
+
deploymentId: z.ZodString;
|
|
21
|
+
status: z.ZodString;
|
|
22
|
+
environment: z.ZodString;
|
|
23
|
+
previewName: z.ZodNullable<z.ZodString>;
|
|
24
|
+
url: z.ZodNullable<z.ZodString>;
|
|
25
|
+
lastError: z.ZodNullable<z.ZodString>;
|
|
26
|
+
createdAt: z.ZodString;
|
|
27
|
+
stoppedAt: z.ZodNullable<z.ZodString>;
|
|
28
|
+
attribution: z.ZodNullable<z.ZodObject<{
|
|
29
|
+
principalType: z.ZodEnum<{
|
|
30
|
+
user: "user";
|
|
31
|
+
"api-key": "api-key";
|
|
32
|
+
system: "system";
|
|
33
|
+
}>;
|
|
34
|
+
displayName: z.ZodString;
|
|
35
|
+
email: z.ZodNullable<z.ZodString>;
|
|
36
|
+
agentName: z.ZodNullable<z.ZodString>;
|
|
37
|
+
redacted: z.ZodBoolean;
|
|
38
|
+
}, z.core.$strip>>;
|
|
39
|
+
}, z.core.$strip>;
|
|
40
|
+
export declare const deploymentsListResponseSchema: z.ZodObject<{
|
|
41
|
+
deployments: z.ZodArray<z.ZodObject<{
|
|
42
|
+
deploymentId: z.ZodString;
|
|
43
|
+
status: z.ZodString;
|
|
44
|
+
environment: z.ZodString;
|
|
45
|
+
previewName: z.ZodNullable<z.ZodString>;
|
|
46
|
+
url: z.ZodNullable<z.ZodString>;
|
|
47
|
+
createdAt: z.ZodString;
|
|
48
|
+
stoppedAt: z.ZodNullable<z.ZodString>;
|
|
49
|
+
attribution: z.ZodNullable<z.ZodObject<{
|
|
50
|
+
principalType: z.ZodEnum<{
|
|
51
|
+
user: "user";
|
|
52
|
+
"api-key": "api-key";
|
|
53
|
+
system: "system";
|
|
54
|
+
}>;
|
|
55
|
+
displayName: z.ZodString;
|
|
56
|
+
email: z.ZodNullable<z.ZodString>;
|
|
57
|
+
agentName: z.ZodNullable<z.ZodString>;
|
|
58
|
+
redacted: z.ZodBoolean;
|
|
59
|
+
}, z.core.$strip>>;
|
|
60
|
+
}, z.core.$strip>>;
|
|
61
|
+
}, z.core.$strip>;
|
|
62
|
+
export declare const buildLogLineSchema: z.ZodObject<{
|
|
63
|
+
text: z.ZodString;
|
|
64
|
+
level: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
|
|
65
|
+
error: "error";
|
|
66
|
+
warning: "warning";
|
|
67
|
+
}>>>;
|
|
68
|
+
timestamp: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
69
|
+
}, z.core.$strip>;
|
|
70
|
+
export declare const deploymentLogsResponseSchema: z.ZodObject<{
|
|
71
|
+
lines: z.ZodNullable<z.ZodArray<z.ZodObject<{
|
|
72
|
+
text: z.ZodString;
|
|
73
|
+
level: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
|
|
74
|
+
error: "error";
|
|
75
|
+
warning: "warning";
|
|
76
|
+
}>>>;
|
|
77
|
+
timestamp: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
78
|
+
}, z.core.$strip>>>;
|
|
79
|
+
supported: z.ZodBoolean;
|
|
80
|
+
deploymentStatus: z.ZodString;
|
|
81
|
+
}, z.core.$strip>;
|