@laioutr/cli 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +238 -1
  3. package/dist/api/deploy.d.ts +65 -0
  4. package/dist/api/deploy.js +34 -0
  5. package/dist/api/fetchRc.d.ts +10 -0
  6. package/dist/api/fetchRc.js +19 -0
  7. package/dist/api/http.d.ts +14 -0
  8. package/dist/api/http.js +28 -0
  9. package/dist/api/releaseAppVersion.d.ts +13 -0
  10. package/dist/api/releaseAppVersion.js +16 -0
  11. package/dist/commands/app/release.d.ts +12 -0
  12. package/dist/commands/app/release.js +63 -0
  13. package/dist/commands/deploy/list.d.ts +31 -0
  14. package/dist/commands/deploy/list.js +54 -0
  15. package/dist/commands/deploy/logs.d.ts +22 -0
  16. package/dist/commands/deploy/logs.js +51 -0
  17. package/dist/commands/deploy/status.d.ts +30 -0
  18. package/dist/commands/deploy/status.js +38 -0
  19. package/dist/commands/deploy/trigger.d.ts +23 -0
  20. package/dist/commands/deploy/trigger.js +129 -0
  21. package/dist/commands/rc/fetch.d.ts +18 -0
  22. package/dist/commands/rc/fetch.js +28 -0
  23. package/dist/commands/rc/update.d.ts +13 -0
  24. package/dist/commands/rc/update.js +33 -0
  25. package/dist/deploy/attribution.d.ts +12 -0
  26. package/dist/deploy/attribution.js +16 -0
  27. package/dist/deploy/log-renderer.d.ts +13 -0
  28. package/dist/deploy/log-renderer.js +20 -0
  29. package/dist/deploy/polling.d.ts +4 -0
  30. package/dist/deploy/polling.js +12 -0
  31. package/dist/deploy/validation.d.ts +81 -0
  32. package/dist/deploy/validation.js +47 -0
  33. package/dist/deploy-base-command.d.ts +11 -0
  34. package/dist/deploy-base-command.js +27 -0
  35. package/dist/format/detail.d.ts +19 -0
  36. package/dist/format/detail.js +18 -0
  37. package/dist/index.d.ts +1 -0
  38. package/dist/index.js +1 -0
  39. package/dist/laioutr-base-command.d.ts +8 -0
  40. package/dist/laioutr-base-command.js +18 -0
  41. package/dist/project.d.ts +14 -0
  42. package/dist/project.js +16 -0
  43. package/dist/rc/validation.d.ts +8 -0
  44. package/dist/rc/validation.js +8 -0
  45. package/oclif.manifest.json +543 -2
  46. package/package.json +4 -2
@@ -0,0 +1,51 @@
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 --deploymentId dep_abc123'];
10
+ static flags = {
11
+ ...DeployBaseCommand.flags,
12
+ deploymentId: Flags.string({
13
+ description: 'Deployment ID',
14
+ required: true,
15
+ }),
16
+ };
17
+ async run() {
18
+ const { flags } = await this.parse(DeployLogs);
19
+ const { organizationSlug, projectSlug } = this.parseProject(flags.project);
20
+ const apiOptions = {
21
+ cockpitApiHost: flags.cockpitApiHost,
22
+ apiKey: flags.key,
23
+ organizationSlug,
24
+ projectSlug,
25
+ deploymentId: flags.deploymentId,
26
+ };
27
+ // Initial fetch
28
+ const initial = await fetchDeploymentLogs(apiOptions);
29
+ if (!initial.supported) {
30
+ this.log("Build logs are not available for this deployment's hosting provider.");
31
+ return initial;
32
+ }
33
+ const isFollowing = !isTerminalStatus(initial.deploymentStatus);
34
+ this.log(`Build log for deployment ${flags.deploymentId}${isFollowing ? ' (following)' : ''}`);
35
+ this.log('');
36
+ const renderer = new LogRenderer();
37
+ renderer.render(initial.lines, (msg) => this.log(msg));
38
+ if (!isFollowing) {
39
+ return initial;
40
+ }
41
+ // Follow mode — poll until terminal
42
+ while (true) {
43
+ await sleep(POLL_INTERVAL_MS);
44
+ const result = await fetchDeploymentLogs(apiOptions);
45
+ renderer.render(result.lines, (msg) => this.log(msg));
46
+ if (isTerminalStatus(result.deploymentStatus)) {
47
+ return result;
48
+ }
49
+ }
50
+ }
51
+ }
@@ -0,0 +1,30 @@
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
+ deploymentId: 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
+ cockpitApiHost: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
11
+ cwd: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
12
+ };
13
+ run(): Promise<{
14
+ deploymentId: string;
15
+ status: string;
16
+ environment: string;
17
+ previewName: string | null;
18
+ url: string | null;
19
+ lastError: string | null;
20
+ createdAt: string;
21
+ stoppedAt: string | null;
22
+ attribution: {
23
+ principalType: "user" | "api-key" | "system";
24
+ displayName: string;
25
+ email: string | null;
26
+ agentName: string | null;
27
+ redacted: boolean;
28
+ } | null;
29
+ }>;
30
+ }
@@ -0,0 +1,38 @@
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 --deploymentId dep_abc123'];
10
+ static flags = {
11
+ ...DeployBaseCommand.flags,
12
+ deploymentId: Flags.string({
13
+ description: 'Deployment ID',
14
+ required: true,
15
+ }),
16
+ };
17
+ async run() {
18
+ const { flags } = await this.parse(DeployStatus);
19
+ const { organizationSlug, projectSlug } = this.parseProject(flags.project);
20
+ const status = await fetchDeploymentStatus({
21
+ cockpitApiHost: flags.cockpitApiHost,
22
+ apiKey: flags.key,
23
+ organizationSlug,
24
+ projectSlug,
25
+ deploymentId: flags.deploymentId,
26
+ });
27
+ this.log(formatDetail([
28
+ ['Status', status.status],
29
+ ['Environment', status.environment],
30
+ ['Preview', status.previewName],
31
+ ['URL', status.url],
32
+ ['Created by', formatAttribution(status.attribution)],
33
+ ['Created at', status.createdAt],
34
+ ['Stopped at', status.stoppedAt],
35
+ ], { title: `Deployment ${status.deploymentId}` }));
36
+ return status;
37
+ }
38
+ }
@@ -0,0 +1,23 @@
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
+ cockpitApiHost: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
13
+ cwd: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
14
+ };
15
+ run(): Promise<{
16
+ deploymentId: string;
17
+ status: string;
18
+ environment: string;
19
+ previewName: string | null;
20
+ }>;
21
+ private pollWithSpinner;
22
+ private pollWithLogs;
23
+ }
@@ -0,0 +1,129 @@
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.cockpitApiHost,
35
+ apiKey: flags.key,
36
+ organizationSlug,
37
+ projectSlug,
38
+ };
39
+ // A named preview requires a non-empty name — the deploy route rejects an
40
+ // empty `previewName`, and an unnamed preview is not supported by this contract.
41
+ if (flags.preview !== undefined && flags.preview.trim() === '') {
42
+ this.error('--preview requires a non-empty name');
43
+ }
44
+ const isPreview = flags.preview !== undefined;
45
+ const label = isPreview ? `preview deployment for ${flags.project} (${flags.preview})` : `deployment for ${flags.project}`;
46
+ this.log(`Triggering ${label}...`);
47
+ // Trigger
48
+ const result = await triggerDeployment({
49
+ ...apiOptions,
50
+ previewName: flags.preview,
51
+ });
52
+ this.log(`Deployment ID: ${result.deploymentId}`);
53
+ this.log('');
54
+ if (!flags.wait) {
55
+ this.log(`Status: ${result.status}`);
56
+ this.log('');
57
+ this.log('Deployment triggered. Use `laioutr deploy status` to check progress.');
58
+ return result;
59
+ }
60
+ // Poll until terminal
61
+ if (flags.logs) {
62
+ return this.pollWithLogs(apiOptions, result.deploymentId);
63
+ }
64
+ return this.pollWithSpinner(apiOptions, result.deploymentId);
65
+ }
66
+ async pollWithSpinner(apiOptions, deploymentId) {
67
+ let lastStatus = '';
68
+ while (true) {
69
+ const status = await fetchDeploymentStatus({ ...apiOptions, deploymentId });
70
+ if (status.status !== lastStatus) {
71
+ if (lastStatus)
72
+ ux.action.stop();
73
+ if (!isTerminalStatus(status.status)) {
74
+ ux.action.start(`Status: ${status.status}`);
75
+ }
76
+ lastStatus = status.status;
77
+ }
78
+ if (isTerminalStatus(status.status)) {
79
+ ux.action.stop();
80
+ this.log('');
81
+ // `not_available` is a success outcome: the provider does not report
82
+ // live status, so the deployment is promoted straight to live. Only
83
+ // `error`/`canceled` (and any unexpected terminal status) are failures.
84
+ if (status.status === 'success' || status.status === 'not_available') {
85
+ this.log(status.status === 'success' ? '✓ Deployment successful' : '✓ Deployment live (provider does not report build status)');
86
+ if (status.url)
87
+ this.log(` URL: ${status.url}`);
88
+ }
89
+ else {
90
+ this.log(`✗ Deployment ${status.status}`);
91
+ if (status.lastError)
92
+ this.log(` Error: ${status.lastError}`);
93
+ this.exit(1);
94
+ }
95
+ return status;
96
+ }
97
+ await sleep(POLL_INTERVAL_MS);
98
+ }
99
+ }
100
+ async pollWithLogs(apiOptions, deploymentId) {
101
+ const renderer = new LogRenderer();
102
+ let lastStatus = '';
103
+ while (true) {
104
+ const logsResult = await fetchDeploymentLogs({ ...apiOptions, deploymentId });
105
+ if (logsResult.deploymentStatus !== lastStatus) {
106
+ this.log(`── Status: ${logsResult.deploymentStatus} ──`);
107
+ lastStatus = logsResult.deploymentStatus;
108
+ }
109
+ renderer.render(logsResult.lines, (msg) => this.log(msg));
110
+ if (isTerminalStatus(logsResult.deploymentStatus)) {
111
+ this.log('');
112
+ // `success` and `not_available` are both live outcomes (see pollWithSpinner);
113
+ // fetch the final status for the URL. Everything else is a failure.
114
+ if (logsResult.deploymentStatus === 'success' || logsResult.deploymentStatus === 'not_available') {
115
+ const finalStatus = await fetchDeploymentStatus({ ...apiOptions, deploymentId });
116
+ this.log(logsResult.deploymentStatus === 'success' ?
117
+ '✓ Deployment successful'
118
+ : '✓ Deployment live (provider does not report build status)');
119
+ if (finalStatus.url)
120
+ this.log(` URL: ${finalStatus.url}`);
121
+ return finalStatus;
122
+ }
123
+ this.log(`✗ Deployment ${logsResult.deploymentStatus}`);
124
+ this.exit(1);
125
+ }
126
+ await sleep(POLL_INTERVAL_MS);
127
+ }
128
+ }
129
+ }
@@ -0,0 +1,18 @@
1
+ import LaioutrBaseCommand from '../../laioutr-base-command.js';
2
+ export default class ProjectRcFetch extends LaioutrBaseCommand {
3
+ static args: {
4
+ fileName: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
5
+ };
6
+ static description: string;
7
+ static examples: string[];
8
+ static flags: {
9
+ environmentName: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
10
+ projectSecret: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
11
+ project: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
12
+ cockpitApiHost: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
13
+ cwd: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
14
+ };
15
+ static aliases: string[];
16
+ static deprecateAliases: boolean;
17
+ run(): Promise<void>;
18
+ }
@@ -0,0 +1,28 @@
1
+ import { writeFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { Args, Flags } from '@oclif/core';
4
+ import { fetchRc } from '../../api/fetchRc.js';
5
+ import LaioutrBaseCommand from '../../laioutr-base-command.js';
6
+ export default class ProjectRcFetch extends LaioutrBaseCommand {
7
+ static args = {
8
+ ...LaioutrBaseCommand.args,
9
+ fileName: Args.string({ default: 'laioutrrc.json', name: 'fileName', required: false }),
10
+ };
11
+ static description = 'Fetches the laioutrrc.json of a project from the cockpit api.';
12
+ static examples = ['<%= config.bin %> <%= command.id %>'];
13
+ static flags = {
14
+ ...LaioutrBaseCommand.flags,
15
+ environmentName: Flags.string({ char: 'e', default: 'main', description: 'environment name', env: 'ENVIRONMENT_NAME', required: true }),
16
+ projectSecret: Flags.string({ char: 's', description: 'project secret', env: 'PROJECT_SECRET', required: true }),
17
+ project: Flags.string({ char: 'p', description: '<organization slug>/<project slug>', env: 'PROJECT', required: true }),
18
+ };
19
+ static aliases = ['project:fetch-rc'];
20
+ static deprecateAliases = true;
21
+ async run() {
22
+ const { args, flags } = await this.parse(ProjectRcFetch);
23
+ const rc = await fetchRc(flags);
24
+ const filePath = path.join(flags.cwd, args.fileName);
25
+ this.log('Writing RC file', filePath);
26
+ return writeFile(filePath, rc);
27
+ }
28
+ }
@@ -0,0 +1,13 @@
1
+ import LaioutrBaseCommand from '../../laioutr-base-command.js';
2
+ export default class ProjectRcUpdate extends LaioutrBaseCommand {
3
+ static args: {
4
+ fileName: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
5
+ };
6
+ static description: string;
7
+ static examples: string[];
8
+ static flags: {
9
+ cockpitApiHost: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
10
+ cwd: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
11
+ };
12
+ run(): Promise<void>;
13
+ }
@@ -0,0 +1,33 @@
1
+ import { readFile, writeFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { Args } from '@oclif/core';
4
+ import { fetchRc } from '../../api/fetchRc.js';
5
+ import LaioutrBaseCommand from '../../laioutr-base-command.js';
6
+ import { rcWithLaioutrMetaSchema } from '../../rc/validation.js';
7
+ export default class ProjectRcUpdate extends LaioutrBaseCommand {
8
+ static args = {
9
+ ...LaioutrBaseCommand.args,
10
+ fileName: Args.string({ default: 'laioutrrc.json', name: 'fileName', required: false }),
11
+ };
12
+ static description = 'Updates an existing laioutrrc.json file with the latest from the cockpit api.';
13
+ static examples = ['<%= config.bin %> <%= command.id %>'];
14
+ static flags = {
15
+ ...LaioutrBaseCommand.flags,
16
+ };
17
+ async run() {
18
+ const { args, flags } = await this.parse(ProjectRcUpdate);
19
+ const currentRcRaw = await readFile(path.join(flags.cwd, args.fileName), 'utf-8');
20
+ const currentRc = rcWithLaioutrMetaSchema.parse(JSON.parse(currentRcRaw));
21
+ const options = {
22
+ ...flags,
23
+ project: currentRc.laioutr.projectSlug,
24
+ projectSecret: currentRc.laioutr.projectSecretKey,
25
+ environmentName: currentRc.laioutr.environmentName,
26
+ };
27
+ this.log('Fetching latest RC for project', options.project);
28
+ const rc = await fetchRc(options);
29
+ const filePath = path.join(flags.cwd, args.fileName);
30
+ this.log('Writing RC file', filePath);
31
+ return writeFile(filePath, rc);
32
+ }
33
+ }
@@ -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,20 @@
1
+ export class LogRenderer {
2
+ lastIndex = 0;
3
+ /**
4
+ * Renders new log lines since the last call.
5
+ * Returns the number of new lines rendered.
6
+ */
7
+ render(lines, log) {
8
+ if (!lines)
9
+ return 0;
10
+ const newLines = lines.slice(this.lastIndex);
11
+ for (const line of newLines) {
12
+ log(line.text);
13
+ }
14
+ this.lastIndex = lines.length;
15
+ return newLines.length;
16
+ }
17
+ reset() {
18
+ this.lastIndex = 0;
19
+ }
20
+ }
@@ -0,0 +1,4 @@
1
+ declare const POLL_INTERVAL_MS = 5000;
2
+ export declare function isTerminalStatus(status: string): boolean;
3
+ export declare function sleep(ms: number): Promise<void>;
4
+ export { POLL_INTERVAL_MS };
@@ -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>;
@@ -0,0 +1,47 @@
1
+ import { z } from 'zod/v4';
2
+ export const deployTriggerResponseSchema = z.object({
3
+ deploymentId: z.string(),
4
+ status: z.string(),
5
+ environment: z.string(),
6
+ previewName: z.string().nullable(),
7
+ });
8
+ export const attributionSchema = z.object({
9
+ principalType: z.enum(['user', 'api-key', 'system']),
10
+ displayName: z.string(),
11
+ email: z.string().nullable(),
12
+ agentName: z.string().nullable(),
13
+ redacted: z.boolean(),
14
+ });
15
+ export const deploymentStatusResponseSchema = z.object({
16
+ deploymentId: z.string(),
17
+ status: z.string(),
18
+ environment: z.string(),
19
+ previewName: z.string().nullable(),
20
+ url: z.string().nullable(),
21
+ lastError: z.string().nullable(),
22
+ createdAt: z.string(),
23
+ stoppedAt: z.string().nullable(),
24
+ attribution: attributionSchema.nullable(),
25
+ });
26
+ export const deploymentsListResponseSchema = z.object({
27
+ deployments: z.array(z.object({
28
+ deploymentId: z.string(),
29
+ status: z.string(),
30
+ environment: z.string(),
31
+ previewName: z.string().nullable(),
32
+ url: z.string().nullable(),
33
+ createdAt: z.string(),
34
+ stoppedAt: z.string().nullable(),
35
+ attribution: attributionSchema.nullable(),
36
+ })),
37
+ });
38
+ export const buildLogLineSchema = z.object({
39
+ text: z.string(),
40
+ level: z.enum(['error', 'warning']).nullable().optional(),
41
+ timestamp: z.number().nullable().optional(),
42
+ });
43
+ export const deploymentLogsResponseSchema = z.object({
44
+ lines: z.array(buildLogLineSchema).nullable(),
45
+ supported: z.boolean(),
46
+ deploymentStatus: z.string(),
47
+ });
@@ -0,0 +1,11 @@
1
+ import LaioutrBaseCommand from './laioutr-base-command.js';
2
+ import type { ProjectSlugs } from './project.js';
3
+ export default abstract class DeployBaseCommand extends LaioutrBaseCommand {
4
+ static flags: {
5
+ project: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
6
+ key: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
7
+ cockpitApiHost: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
8
+ cwd: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
9
+ };
10
+ protected parseProject(project: string): ProjectSlugs;
11
+ }
@@ -0,0 +1,27 @@
1
+ import { Flags } from '@oclif/core';
2
+ import LaioutrBaseCommand from './laioutr-base-command.js';
3
+ import { parseProjectSlug } from './project.js';
4
+ export default class DeployBaseCommand extends LaioutrBaseCommand {
5
+ static flags = {
6
+ ...LaioutrBaseCommand.flags,
7
+ project: Flags.string({
8
+ char: 'p',
9
+ description: 'Project identifier in org/project format',
10
+ env: 'PROJECT',
11
+ required: true,
12
+ }),
13
+ key: Flags.string({
14
+ char: 'k',
15
+ description: 'Organization API key with project:deploy scope',
16
+ env: 'LAIOUTR_API_KEY',
17
+ required: true,
18
+ }),
19
+ };
20
+ parseProject(project) {
21
+ const parsed = parseProjectSlug(project);
22
+ if (!parsed) {
23
+ this.error('--project must be in org/project format');
24
+ }
25
+ return parsed;
26
+ }
27
+ }
@@ -0,0 +1,19 @@
1
+ export type DetailField = [label: string, value: string | null | undefined];
2
+ export interface FormatDetailOptions {
3
+ /** Optional heading printed above the aligned fields. */
4
+ title?: string;
5
+ /** Leading indent for each field line. Defaults to two spaces. */
6
+ indent?: string;
7
+ }
8
+ /**
9
+ * Formats an object's fields as an aligned `label: value` detail block:
10
+ *
11
+ * Deployment dep_abc123
12
+ * Status: success
13
+ * Environment: production
14
+ *
15
+ * Labels are padded so every value starts in the same column. Fields whose
16
+ * value is `null`, `undefined`, or empty are omitted, so a caller can list
17
+ * every possible field inline without pre-filtering the absent ones.
18
+ */
19
+ export declare function formatDetail(fields: DetailField[], options?: FormatDetailOptions): string;