@miosa/cli 1.1.4 → 1.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +118 -0
- package/dist/app-contract.d.ts +92 -0
- package/dist/app-contract.d.ts.map +1 -0
- package/dist/app-contract.js +219 -0
- package/dist/app-contract.js.map +1 -0
- package/dist/app-manifest.d.ts +50 -0
- package/dist/app-manifest.d.ts.map +1 -1
- package/dist/app-manifest.js +106 -0
- package/dist/app-manifest.js.map +1 -1
- package/dist/app-operation.d.ts +23 -0
- package/dist/app-operation.d.ts.map +1 -0
- package/dist/app-operation.js +49 -0
- package/dist/app-operation.js.map +1 -0
- package/dist/app-release.d.ts +132 -0
- package/dist/app-release.d.ts.map +1 -0
- package/dist/app-release.js +294 -0
- package/dist/app-release.js.map +1 -0
- package/dist/bin/miosa.js +13 -0
- package/dist/bin/miosa.js.map +1 -1
- package/dist/commands/app.d.ts +35 -0
- package/dist/commands/app.d.ts.map +1 -1
- package/dist/commands/app.js +726 -1
- package/dist/commands/app.js.map +1 -1
- package/dist/commands/capabilities.d.ts.map +1 -1
- package/dist/commands/capabilities.js +82 -7
- package/dist/commands/capabilities.js.map +1 -1
- package/dist/commands/context.d.ts.map +1 -1
- package/dist/commands/context.js +3 -1
- package/dist/commands/context.js.map +1 -1
- package/dist/commands/docker-deploy.d.ts.map +1 -1
- package/dist/commands/docker-deploy.js +174 -10
- package/dist/commands/docker-deploy.js.map +1 -1
- package/dist/commands/operating-contract.d.ts +3 -0
- package/dist/commands/operating-contract.d.ts.map +1 -0
- package/dist/commands/operating-contract.js +651 -0
- package/dist/commands/operating-contract.js.map +1 -0
- package/dist/commands/pull.d.ts +11 -0
- package/dist/commands/pull.d.ts.map +1 -1
- package/dist/commands/pull.js +3 -3
- package/dist/commands/pull.js.map +1 -1
- package/dist/commands/releases.d.ts.map +1 -1
- package/dist/commands/releases.js +12 -4
- package/dist/commands/releases.js.map +1 -1
- package/dist/commands/sandbox.d.ts +28 -0
- package/dist/commands/sandbox.d.ts.map +1 -1
- package/dist/commands/sandbox.js +1 -1
- package/dist/commands/sandbox.js.map +1 -1
- package/dist/config.d.ts +2 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +27 -5
- package/dist/config.js.map +1 -1
- package/dist/types.d.ts +7 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -86,6 +86,124 @@ Use `m7i.2xlarge` for the supported baseline tier.
|
|
|
86
86
|
|
|
87
87
|
## Deploy — 60 seconds to first deploy
|
|
88
88
|
|
|
89
|
+
### Safe application release workflow
|
|
90
|
+
|
|
91
|
+
Use the application workflow for normal preview, production, and recovery operations.
|
|
92
|
+
It keeps the source directory linked to one exact MIOSA deployment and refuses to report production success until the immutable release, artifact digest, route contract, App Engine placement, database attachment, and required configuration are proven.
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
miosa app link . --app <deployment-id> --environment production
|
|
96
|
+
miosa app pull .
|
|
97
|
+
miosa app preview . --json
|
|
98
|
+
miosa app verify <release-id> . --json
|
|
99
|
+
miosa app promote <release-id> . --yes --json
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Promotion and rollback always target an exact release ID.
|
|
103
|
+
They persist an operation record and idempotency key under `.miosa/operations`, so a request timeout can be inspected or resumed without creating duplicate deployment work.
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
miosa app recover <operation-id> . --json
|
|
107
|
+
miosa app recover <operation-id> . --resume --json
|
|
108
|
+
miosa app rollback <release-id> . --yes --json
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Declare application-specific acceptance requirements in `.miosa/acceptance.json`.
|
|
112
|
+
The default contract requires HTTP 200 from `/`.
|
|
113
|
+
|
|
114
|
+
```json
|
|
115
|
+
{
|
|
116
|
+
"schema_version": 1,
|
|
117
|
+
"routes": [
|
|
118
|
+
{
|
|
119
|
+
"id": "access",
|
|
120
|
+
"path": "/access",
|
|
121
|
+
"expected_status": [200],
|
|
122
|
+
"body_contains": ["Open the working brief"],
|
|
123
|
+
"content_type": "text/html",
|
|
124
|
+
"required": true
|
|
125
|
+
}
|
|
126
|
+
],
|
|
127
|
+
"required_env": ["DATABASE_URL", "BRIEF_AUTH_SECRET"],
|
|
128
|
+
"database": {
|
|
129
|
+
"required": true,
|
|
130
|
+
"health_path": "/api/health"
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Each verification writes a durable, machine-readable receipt under `.miosa/receipts`.
|
|
136
|
+
That receipt is the acceptance evidence for the release.
|
|
137
|
+
|
|
138
|
+
### Capability-gated release contract
|
|
139
|
+
|
|
140
|
+
Use `miosa.app.yml` as the primary typed contract when a release depends on routes, secrets, databases, migrations, connectors, jobs, business capabilities, or approval policy.
|
|
141
|
+
|
|
142
|
+
Every mutation resolves one exact organization, workspace, application, environment, and deployment.
|
|
143
|
+
|
|
144
|
+
The organization option is the canonical name for the existing tenant identity, while `--tenant` remains a compatibility alias.
|
|
145
|
+
|
|
146
|
+
```yaml
|
|
147
|
+
schema_version: 1
|
|
148
|
+
name: clinic-intake
|
|
149
|
+
organization: 06737e41-32a2-4c66-bf42-a2e123456789
|
|
150
|
+
workspace: e0211a62-b08e-402a-a171-b6c123456789
|
|
151
|
+
application: clinic-intake
|
|
152
|
+
environment: production
|
|
153
|
+
|
|
154
|
+
services:
|
|
155
|
+
web:
|
|
156
|
+
command: npm start
|
|
157
|
+
port: 3000
|
|
158
|
+
|
|
159
|
+
capabilities:
|
|
160
|
+
routes:
|
|
161
|
+
- id: homepage
|
|
162
|
+
path: /
|
|
163
|
+
expected_status: [200]
|
|
164
|
+
secrets:
|
|
165
|
+
- SESSION_SECRET
|
|
166
|
+
database:
|
|
167
|
+
required: true
|
|
168
|
+
health_path: /api/health
|
|
169
|
+
migration:
|
|
170
|
+
required: true
|
|
171
|
+
compatibility: backward_compatible
|
|
172
|
+
connectors:
|
|
173
|
+
- id: oauth
|
|
174
|
+
jobs:
|
|
175
|
+
- id: daily-sync
|
|
176
|
+
business:
|
|
177
|
+
- id: customer-sync
|
|
178
|
+
path: /api/capabilities/customer-sync
|
|
179
|
+
expected_status: [200]
|
|
180
|
+
|
|
181
|
+
policy:
|
|
182
|
+
approvals_required: 1
|
|
183
|
+
allowed_environments: [production]
|
|
184
|
+
require_immutable_release: true
|
|
185
|
+
require_rollback_path: true
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
The change workflow saves the same immutable plan locally and in the control-plane operation ledger.
|
|
189
|
+
|
|
190
|
+
Approvals are bound to the plan fingerprint, and exact apply is idempotent.
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
miosa blueprint validate . --json
|
|
194
|
+
miosa changes plan <release-id> . --json
|
|
195
|
+
miosa changes approve <plan-id> . --actor <identity> --json
|
|
196
|
+
miosa changes apply <plan-id> . --json
|
|
197
|
+
miosa evidence show <receipt-id> . --json
|
|
198
|
+
miosa drift detect <plan-id> . --json
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Pre-promotion gates check immutable release identity, secrets, database attachment, migration evidence, connectors, jobs, policy, and declared business capability evidence.
|
|
202
|
+
|
|
203
|
+
Post-promotion acceptance verifies the active release, running artifact, public routes, data bindings, placement, and declared capabilities.
|
|
204
|
+
|
|
205
|
+
If acceptance fails, the CLI requests the exact saved rollback version and records whether restoration was confirmed.
|
|
206
|
+
|
|
89
207
|
Point the CLI at any repo and it handles the rest: framework detection, build wiring, GitHub webhook setup, and live log streaming.
|
|
90
208
|
|
|
91
209
|
For production app hosting, MIOSA recommends App Engine. It runs apps on the
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { type AppCapabilitiesManifest, type AppPolicyManifest, type MiosaAppManifest } from "./app-manifest.js";
|
|
2
|
+
export interface ApplicationScope {
|
|
3
|
+
organization: string;
|
|
4
|
+
workspace: string;
|
|
5
|
+
application: string;
|
|
6
|
+
environment: string;
|
|
7
|
+
deployment_id: string;
|
|
8
|
+
}
|
|
9
|
+
export type CandidateReleaseState = "planned" | "approved" | "applying" | "verified" | "blocked" | "failed";
|
|
10
|
+
export interface PlanApproval {
|
|
11
|
+
actor: string;
|
|
12
|
+
approved_at: string;
|
|
13
|
+
plan_fingerprint: string;
|
|
14
|
+
}
|
|
15
|
+
export interface SavedApplicationPlan {
|
|
16
|
+
schema_version: 1;
|
|
17
|
+
plan_id: string;
|
|
18
|
+
fingerprint: string;
|
|
19
|
+
control_plane_plan_id?: string;
|
|
20
|
+
scope: ApplicationScope;
|
|
21
|
+
release: {
|
|
22
|
+
id: string;
|
|
23
|
+
version_id: string;
|
|
24
|
+
artifact_sha256: string;
|
|
25
|
+
rollback_version_id: string | null;
|
|
26
|
+
};
|
|
27
|
+
desired_state: {
|
|
28
|
+
route: {
|
|
29
|
+
public_url: string | null;
|
|
30
|
+
};
|
|
31
|
+
archive: {
|
|
32
|
+
artifact_sha256: string;
|
|
33
|
+
};
|
|
34
|
+
host: {
|
|
35
|
+
id: string | null;
|
|
36
|
+
status: string;
|
|
37
|
+
appliance_status: string;
|
|
38
|
+
};
|
|
39
|
+
database: {
|
|
40
|
+
attached: boolean;
|
|
41
|
+
};
|
|
42
|
+
connectors: {
|
|
43
|
+
ids: string[];
|
|
44
|
+
};
|
|
45
|
+
jobs: {
|
|
46
|
+
ids: string[];
|
|
47
|
+
};
|
|
48
|
+
policy: {
|
|
49
|
+
fingerprint: string;
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
capabilities: AppCapabilitiesManifest;
|
|
53
|
+
policy: AppPolicyManifest;
|
|
54
|
+
state: CandidateReleaseState;
|
|
55
|
+
approvals: PlanApproval[];
|
|
56
|
+
created_at: string;
|
|
57
|
+
updated_at: string;
|
|
58
|
+
}
|
|
59
|
+
export interface DriftItem {
|
|
60
|
+
path: string;
|
|
61
|
+
expected: unknown;
|
|
62
|
+
actual: unknown;
|
|
63
|
+
severity: "blocking" | "warning";
|
|
64
|
+
reconcile: string;
|
|
65
|
+
}
|
|
66
|
+
export declare function contractFingerprint(value: unknown): string;
|
|
67
|
+
export declare function resolveApplicationContract(appDir: string, link: {
|
|
68
|
+
deploymentId?: string;
|
|
69
|
+
name?: string;
|
|
70
|
+
environment?: string;
|
|
71
|
+
workspaceId?: string;
|
|
72
|
+
organizationId?: string;
|
|
73
|
+
}): {
|
|
74
|
+
manifest: MiosaAppManifest;
|
|
75
|
+
scope: ApplicationScope;
|
|
76
|
+
capabilities: AppCapabilitiesManifest;
|
|
77
|
+
policy: AppPolicyManifest;
|
|
78
|
+
};
|
|
79
|
+
export declare function createSavedPlan(appDir: string, input: {
|
|
80
|
+
scope: ApplicationScope;
|
|
81
|
+
release: SavedApplicationPlan["release"];
|
|
82
|
+
desired_state: SavedApplicationPlan["desired_state"];
|
|
83
|
+
capabilities: AppCapabilitiesManifest;
|
|
84
|
+
policy: AppPolicyManifest;
|
|
85
|
+
}): SavedApplicationPlan;
|
|
86
|
+
export declare function savePlan(appDir: string, plan: SavedApplicationPlan): string;
|
|
87
|
+
export declare function loadPlan(appDir: string, planId: string): SavedApplicationPlan;
|
|
88
|
+
export declare function assertPlanIntegrity(plan: SavedApplicationPlan): void;
|
|
89
|
+
export declare function approvePlan(appDir: string, plan: SavedApplicationPlan, actor: string): SavedApplicationPlan;
|
|
90
|
+
export declare function assertPlanApplicable(plan: SavedApplicationPlan, actualScope: ApplicationScope): void;
|
|
91
|
+
export declare function detectContractDrift(expected: Record<string, unknown>, actual: Record<string, unknown>, prefix?: string): DriftItem[];
|
|
92
|
+
//# sourceMappingURL=app-contract.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-contract.d.ts","sourceRoot":"","sources":["../src/app-contract.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { loadAppManifest, } from "./app-manifest.js";
|
|
5
|
+
import { UserError } from "./errors.js";
|
|
6
|
+
const DEFAULT_CAPABILITIES = {
|
|
7
|
+
routes: [
|
|
8
|
+
{
|
|
9
|
+
id: "root",
|
|
10
|
+
path: "/",
|
|
11
|
+
expected_status: [200],
|
|
12
|
+
required: true,
|
|
13
|
+
},
|
|
14
|
+
],
|
|
15
|
+
secrets: [],
|
|
16
|
+
connectors: [],
|
|
17
|
+
jobs: [],
|
|
18
|
+
business: [],
|
|
19
|
+
};
|
|
20
|
+
const DEFAULT_POLICY = {
|
|
21
|
+
approvals_required: 1,
|
|
22
|
+
allowed_environments: [],
|
|
23
|
+
require_immutable_release: true,
|
|
24
|
+
require_rollback_path: true,
|
|
25
|
+
};
|
|
26
|
+
function stable(value) {
|
|
27
|
+
if (Array.isArray(value))
|
|
28
|
+
return value.map(stable);
|
|
29
|
+
if (!value || typeof value !== "object")
|
|
30
|
+
return value;
|
|
31
|
+
return Object.fromEntries(Object.entries(value)
|
|
32
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
33
|
+
.map(([key, nested]) => [key, stable(nested)]));
|
|
34
|
+
}
|
|
35
|
+
export function contractFingerprint(value) {
|
|
36
|
+
return createHash("sha256")
|
|
37
|
+
.update(JSON.stringify(stable(value)))
|
|
38
|
+
.digest("hex");
|
|
39
|
+
}
|
|
40
|
+
export function resolveApplicationContract(appDir, link) {
|
|
41
|
+
const loaded = loadAppManifest(appDir);
|
|
42
|
+
const manifest = loaded?.manifest ?? {};
|
|
43
|
+
const conflicts = [
|
|
44
|
+
conflict("organization", manifest.organization, link.organizationId),
|
|
45
|
+
conflict("workspace", manifest.workspace, link.workspaceId),
|
|
46
|
+
conflict("application", manifest.application, link.name),
|
|
47
|
+
conflict("environment", manifest.environment, link.environment),
|
|
48
|
+
].filter((value) => Boolean(value));
|
|
49
|
+
if (conflicts.length > 0) {
|
|
50
|
+
throw new UserError(`Application scope conflicts with the linked deployment: ${conflicts.join("; ")}.`, "Update miosa.app.yml or relink the directory. MIOSA will not guess mutation scope.");
|
|
51
|
+
}
|
|
52
|
+
const organization = manifest.organization ?? link.organizationId;
|
|
53
|
+
const workspace = manifest.workspace ?? link.workspaceId;
|
|
54
|
+
const application = manifest.application ?? link.name;
|
|
55
|
+
const environment = manifest.environment ?? link.environment;
|
|
56
|
+
const deploymentId = link.deploymentId;
|
|
57
|
+
const missing = [
|
|
58
|
+
!organization ? "organization" : null,
|
|
59
|
+
!workspace ? "workspace" : null,
|
|
60
|
+
!application ? "application" : null,
|
|
61
|
+
!environment ? "environment" : null,
|
|
62
|
+
!deploymentId ? "deployment" : null,
|
|
63
|
+
].filter((value) => Boolean(value));
|
|
64
|
+
if (missing.length > 0) {
|
|
65
|
+
throw new UserError(`Application scope is ambiguous: missing ${missing.join(", ")}.`, "Declare organization, workspace, application, and environment in miosa.app.yml, then run `miosa app link`.");
|
|
66
|
+
}
|
|
67
|
+
if (!organization ||
|
|
68
|
+
!workspace ||
|
|
69
|
+
!application ||
|
|
70
|
+
!environment ||
|
|
71
|
+
!deploymentId) {
|
|
72
|
+
throw new UserError("Application scope could not be resolved.");
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
manifest,
|
|
76
|
+
scope: {
|
|
77
|
+
organization,
|
|
78
|
+
workspace,
|
|
79
|
+
application,
|
|
80
|
+
environment,
|
|
81
|
+
deployment_id: deploymentId,
|
|
82
|
+
},
|
|
83
|
+
capabilities: manifest.capabilities ?? DEFAULT_CAPABILITIES,
|
|
84
|
+
policy: { ...DEFAULT_POLICY, ...manifest.policy },
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function conflict(field, declared, linked) {
|
|
88
|
+
return declared && linked && declared !== linked
|
|
89
|
+
? `${field} declares ${declared} but link resolves ${linked}`
|
|
90
|
+
: null;
|
|
91
|
+
}
|
|
92
|
+
function planDir(appDir) {
|
|
93
|
+
return path.join(appDir, ".miosa", "plans");
|
|
94
|
+
}
|
|
95
|
+
function planPayload(plan) {
|
|
96
|
+
return {
|
|
97
|
+
schema_version: plan.schema_version,
|
|
98
|
+
plan_id: plan.plan_id,
|
|
99
|
+
scope: plan.scope,
|
|
100
|
+
release: plan.release,
|
|
101
|
+
desired_state: plan.desired_state,
|
|
102
|
+
capabilities: plan.capabilities,
|
|
103
|
+
policy: plan.policy,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
export function createSavedPlan(appDir, input) {
|
|
107
|
+
if (!input.release.version_id || !input.release.artifact_sha256) {
|
|
108
|
+
throw new UserError("A saved plan requires an immutable version ID and artifact digest.");
|
|
109
|
+
}
|
|
110
|
+
if (input.policy.allowed_environments.length > 0 &&
|
|
111
|
+
!input.policy.allowed_environments.includes(input.scope.environment)) {
|
|
112
|
+
throw new UserError(`Policy does not allow environment ${input.scope.environment}.`);
|
|
113
|
+
}
|
|
114
|
+
if (input.policy.require_rollback_path && !input.release.rollback_version_id) {
|
|
115
|
+
throw new UserError("Policy requires an exact rollback version before this plan can be saved.");
|
|
116
|
+
}
|
|
117
|
+
const now = new Date().toISOString();
|
|
118
|
+
const base = {
|
|
119
|
+
schema_version: 1,
|
|
120
|
+
plan_id: `plan_${randomUUID()}`,
|
|
121
|
+
scope: input.scope,
|
|
122
|
+
release: input.release,
|
|
123
|
+
desired_state: input.desired_state,
|
|
124
|
+
capabilities: input.capabilities,
|
|
125
|
+
policy: input.policy,
|
|
126
|
+
};
|
|
127
|
+
const plan = {
|
|
128
|
+
...base,
|
|
129
|
+
fingerprint: contractFingerprint(planPayload(base)),
|
|
130
|
+
state: "planned",
|
|
131
|
+
approvals: [],
|
|
132
|
+
created_at: now,
|
|
133
|
+
updated_at: now,
|
|
134
|
+
};
|
|
135
|
+
savePlan(appDir, plan);
|
|
136
|
+
return plan;
|
|
137
|
+
}
|
|
138
|
+
export function savePlan(appDir, plan) {
|
|
139
|
+
const dir = planDir(appDir);
|
|
140
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
141
|
+
const file = path.join(dir, `${plan.plan_id}.json`);
|
|
142
|
+
fs.writeFileSync(file, `${JSON.stringify(plan, null, 2)}\n`, { mode: 0o600 });
|
|
143
|
+
return file;
|
|
144
|
+
}
|
|
145
|
+
export function loadPlan(appDir, planId) {
|
|
146
|
+
const file = path.join(planDir(appDir), `${planId}.json`);
|
|
147
|
+
if (!fs.existsSync(file))
|
|
148
|
+
throw new UserError(`Saved plan not found: ${planId}`);
|
|
149
|
+
const plan = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
150
|
+
assertPlanIntegrity(plan);
|
|
151
|
+
return plan;
|
|
152
|
+
}
|
|
153
|
+
export function assertPlanIntegrity(plan) {
|
|
154
|
+
const fingerprint = contractFingerprint(planPayload(plan));
|
|
155
|
+
if (fingerprint !== plan.fingerprint) {
|
|
156
|
+
throw new UserError(`Saved plan ${plan.plan_id} has changed since it was created.`, "Create and approve a new plan. Exact apply never accepts a modified plan.");
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
export function approvePlan(appDir, plan, actor) {
|
|
160
|
+
assertPlanIntegrity(plan);
|
|
161
|
+
const approval = {
|
|
162
|
+
actor,
|
|
163
|
+
approved_at: new Date().toISOString(),
|
|
164
|
+
plan_fingerprint: plan.fingerprint,
|
|
165
|
+
};
|
|
166
|
+
const approvals = [
|
|
167
|
+
...plan.approvals.filter((item) => item.actor !== actor),
|
|
168
|
+
approval,
|
|
169
|
+
];
|
|
170
|
+
const next = {
|
|
171
|
+
...plan,
|
|
172
|
+
approvals,
|
|
173
|
+
state: approvals.length >= plan.policy.approvals_required
|
|
174
|
+
? "approved"
|
|
175
|
+
: "planned",
|
|
176
|
+
updated_at: approval.approved_at,
|
|
177
|
+
};
|
|
178
|
+
savePlan(appDir, next);
|
|
179
|
+
return next;
|
|
180
|
+
}
|
|
181
|
+
export function assertPlanApplicable(plan, actualScope) {
|
|
182
|
+
assertPlanIntegrity(plan);
|
|
183
|
+
if (contractFingerprint(plan.scope) !== contractFingerprint(actualScope)) {
|
|
184
|
+
throw new UserError(`Saved plan ${plan.plan_id} does not match the active organization, workspace, application, environment, and deployment.`);
|
|
185
|
+
}
|
|
186
|
+
if (plan.approvals.length < plan.policy.approvals_required) {
|
|
187
|
+
throw new UserError(`Saved plan ${plan.plan_id} has ${plan.approvals.length} approvals, but policy requires ${plan.policy.approvals_required}.`);
|
|
188
|
+
}
|
|
189
|
+
if (!plan.approvals.every((item) => item.plan_fingerprint === plan.fingerprint)) {
|
|
190
|
+
throw new UserError(`Saved plan ${plan.plan_id} contains a stale approval.`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
export function detectContractDrift(expected, actual, prefix = "") {
|
|
194
|
+
const items = [];
|
|
195
|
+
for (const key of Object.keys(expected).sort()) {
|
|
196
|
+
const pathName = prefix ? `${prefix}.${key}` : key;
|
|
197
|
+
const expectedValue = expected[key];
|
|
198
|
+
const actualValue = actual[key];
|
|
199
|
+
if (expectedValue &&
|
|
200
|
+
typeof expectedValue === "object" &&
|
|
201
|
+
!Array.isArray(expectedValue) &&
|
|
202
|
+
actualValue &&
|
|
203
|
+
typeof actualValue === "object" &&
|
|
204
|
+
!Array.isArray(actualValue)) {
|
|
205
|
+
items.push(...detectContractDrift(expectedValue, actualValue, pathName));
|
|
206
|
+
}
|
|
207
|
+
else if (contractFingerprint(expectedValue) !== contractFingerprint(actualValue)) {
|
|
208
|
+
items.push({
|
|
209
|
+
path: pathName,
|
|
210
|
+
expected: expectedValue,
|
|
211
|
+
actual: actualValue,
|
|
212
|
+
severity: "blocking",
|
|
213
|
+
reconcile: `restore ${pathName} to the saved plan value`,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return items;
|
|
218
|
+
}
|
|
219
|
+
//# sourceMappingURL=app-contract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-contract.js","sourceRoot":"","sources":["../src/app-contract.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EACL,eAAe,GAIhB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AA6DxC,MAAM,oBAAoB,GAA4B;IACpD,MAAM,EAAE;QACN;YACE,EAAE,EAAE,MAAM;YACV,IAAI,EAAE,GAAG;YACT,eAAe,EAAE,CAAC,GAAG,CAAC;YACtB,QAAQ,EAAE,IAAI;SACf;KACF;IACD,OAAO,EAAE,EAAE;IACX,UAAU,EAAE,EAAE;IACd,IAAI,EAAE,EAAE;IACR,QAAQ,EAAE,EAAE;CACb,CAAC;AAEF,MAAM,cAAc,GAAsB;IACxC,kBAAkB,EAAE,CAAC;IACrB,oBAAoB,EAAE,EAAE;IACxB,yBAAyB,EAAE,IAAI;IAC/B,qBAAqB,EAAE,IAAI;CAC5B,CAAC;AAEF,SAAS,MAAM,CAAC,KAAc;IAC5B,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACnD,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IACtD,OAAO,MAAM,CAAC,WAAW,CACvB,MAAM,CAAC,OAAO,CAAC,KAAgC,CAAC;SAC7C,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;SACpD,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CACjD,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,KAAc;IAChD,OAAO,UAAU,CAAC,QAAQ,CAAC;SACxB,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;SACrC,MAAM,CAAC,KAAK,CAAC,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,0BAA0B,CACxC,MAAc,EACd,IAMC;IAOD,MAAM,MAAM,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;IACvC,MAAM,QAAQ,GAAG,MAAM,EAAE,QAAQ,IAAI,EAAE,CAAC;IACxC,MAAM,SAAS,GAAG;QAChB,QAAQ,CAAC,cAAc,EAAE,QAAQ,CAAC,YAAY,EAAE,IAAI,CAAC,cAAc,CAAC;QACpE,QAAQ,CAAC,WAAW,EAAE,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC,WAAW,CAAC;QAC3D,QAAQ,CAAC,aAAa,EAAE,QAAQ,CAAC,WAAW,EAAE,IAAI,CAAC,IAAI,CAAC;QACxD,QAAQ,CAAC,aAAa,EAAE,QAAQ,CAAC,WAAW,EAAE,IAAI,CAAC,WAAW,CAAC;KAChE,CAAC,MAAM,CAAC,CAAC,KAAK,EAAmB,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;IACrD,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,SAAS,CACjB,2DAA2D,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAClF,oFAAoF,CACrF,CAAC;IACJ,CAAC;IACD,MAAM,YAAY,GAAG,QAAQ,CAAC,YAAY,IAAI,IAAI,CAAC,cAAc,CAAC;IAClE,MAAM,SAAS,GAAG,QAAQ,CAAC,SAAS,IAAI,IAAI,CAAC,WAAW,CAAC;IACzD,MAAM,WAAW,GAAG,QAAQ,CAAC,WAAW,IAAI,IAAI,CAAC,IAAI,CAAC;IACtD,MAAM,WAAW,GAAG,QAAQ,CAAC,WAAW,IAAI,IAAI,CAAC,WAAW,CAAC;IAC7D,MAAM,YAAY,GAAG,IAAI,CAAC,YAAY,CAAC;IACvC,MAAM,OAAO,GAAG;QACd,CAAC,YAAY,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI;QACrC,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI;QAC/B,CAAC,WAAW,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI;QACnC,CAAC,WAAW,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI;QACnC,CAAC,YAAY,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI;KACpC,CAAC,MAAM,CAAC,CAAC,KAAK,EAAmB,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;IACrD,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,SAAS,CACjB,2CAA2C,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAChE,4GAA4G,CAC7G,CAAC;IACJ,CAAC;IACD,IACE,CAAC,YAAY;QACb,CAAC,SAAS;QACV,CAAC,WAAW;QACZ,CAAC,WAAW;QACZ,CAAC,YAAY,EACb,CAAC;QACD,MAAM,IAAI,SAAS,CAAC,0CAA0C,CAAC,CAAC;IAClE,CAAC;IACD,OAAO;QACL,QAAQ;QACR,KAAK,EAAE;YACL,YAAY;YACZ,SAAS;YACT,WAAW;YACX,WAAW;YACX,aAAa,EAAE,YAAY;SAC5B;QACD,YAAY,EAAE,QAAQ,CAAC,YAAY,IAAI,oBAAoB;QAC3D,MAAM,EAAE,EAAE,GAAG,cAAc,EAAE,GAAG,QAAQ,CAAC,MAAM,EAAE;KAClD,CAAC;AACJ,CAAC;AAED,SAAS,QAAQ,CACf,KAAa,EACb,QAA4B,EAC5B,MAA0B;IAE1B,OAAO,QAAQ,IAAI,MAAM,IAAI,QAAQ,KAAK,MAAM;QAC9C,CAAC,CAAC,GAAG,KAAK,aAAa,QAAQ,sBAAsB,MAAM,EAAE;QAC7D,CAAC,CAAC,IAAI,CAAC;AACX,CAAC;AAED,SAAS,OAAO,CAAC,MAAc;IAC7B,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;AAC9C,CAAC;AAED,SAAS,WAAW,CAClB,IAAqG;IAErG,OAAO;QACL,cAAc,EAAE,IAAI,CAAC,cAAc;QACnC,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,aAAa,EAAE,IAAI,CAAC,aAAa;QACjC,YAAY,EAAE,IAAI,CAAC,YAAY;QAC/B,MAAM,EAAE,IAAI,CAAC,MAAM;KACpB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,eAAe,CAC7B,MAAc,EACd,KAMC;IAED,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,UAAU,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,eAAe,EAAE,CAAC;QAChE,MAAM,IAAI,SAAS,CACjB,oEAAoE,CACrE,CAAC;IACJ,CAAC;IACD,IACE,KAAK,CAAC,MAAM,CAAC,oBAAoB,CAAC,MAAM,GAAG,CAAC;QAC5C,CAAC,KAAK,CAAC,MAAM,CAAC,oBAAoB,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,EACpE,CAAC;QACD,MAAM,IAAI,SAAS,CACjB,qCAAqC,KAAK,CAAC,KAAK,CAAC,WAAW,GAAG,CAChE,CAAC;IACJ,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,CAAC,qBAAqB,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,mBAAmB,EAAE,CAAC;QAC7E,MAAM,IAAI,SAAS,CACjB,0EAA0E,CAC3E,CAAC;IACJ,CAAC;IACD,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACrC,MAAM,IAAI,GAAG;QACX,cAAc,EAAE,CAAU;QAC1B,OAAO,EAAE,QAAQ,UAAU,EAAE,EAAE;QAC/B,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,aAAa,EAAE,KAAK,CAAC,aAAa;QAClC,YAAY,EAAE,KAAK,CAAC,YAAY;QAChC,MAAM,EAAE,KAAK,CAAC,MAAM;KACrB,CAAC;IACF,MAAM,IAAI,GAAyB;QACjC,GAAG,IAAI;QACP,WAAW,EAAE,mBAAmB,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;QACnD,KAAK,EAAE,SAAS;QAChB,SAAS,EAAE,EAAE;QACb,UAAU,EAAE,GAAG;QACf,UAAU,EAAE,GAAG;KAChB,CAAC;IACF,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACvB,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,MAAc,EAAE,IAA0B;IACjE,MAAM,GAAG,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5B,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACpD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,OAAO,OAAO,CAAC,CAAC;IACpD,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC9E,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,MAAc,EAAE,MAAc;IACrD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,GAAG,MAAM,OAAO,CAAC,CAAC;IAC1D,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,MAAM,IAAI,SAAS,CAAC,yBAAyB,MAAM,EAAE,CAAC,CAAC;IACjF,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAyB,CAAC;IAC/E,mBAAmB,CAAC,IAAI,CAAC,CAAC;IAC1B,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,IAA0B;IAC5D,MAAM,WAAW,GAAG,mBAAmB,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC;IAC3D,IAAI,WAAW,KAAK,IAAI,CAAC,WAAW,EAAE,CAAC;QACrC,MAAM,IAAI,SAAS,CACjB,cAAc,IAAI,CAAC,OAAO,oCAAoC,EAC9D,2EAA2E,CAC5E,CAAC;IACJ,CAAC;AACH,CAAC;AAED,MAAM,UAAU,WAAW,CACzB,MAAc,EACd,IAA0B,EAC1B,KAAa;IAEb,mBAAmB,CAAC,IAAI,CAAC,CAAC;IAC1B,MAAM,QAAQ,GAAiB;QAC7B,KAAK;QACL,WAAW,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACrC,gBAAgB,EAAE,IAAI,CAAC,WAAW;KACnC,CAAC;IACF,MAAM,SAAS,GAAG;QAChB,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,KAAK,KAAK,CAAC;QACxD,QAAQ;KACT,CAAC;IACF,MAAM,IAAI,GAAyB;QACjC,GAAG,IAAI;QACP,SAAS;QACT,KAAK,EACH,SAAS,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,CAAC,kBAAkB;YAChD,CAAC,CAAC,UAAU;YACZ,CAAC,CAAC,SAAS;QACf,UAAU,EAAE,QAAQ,CAAC,WAAW;KACjC,CAAC;IACF,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACvB,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,oBAAoB,CAClC,IAA0B,EAC1B,WAA6B;IAE7B,mBAAmB,CAAC,IAAI,CAAC,CAAC;IAC1B,IAAI,mBAAmB,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,mBAAmB,CAAC,WAAW,CAAC,EAAE,CAAC;QACzE,MAAM,IAAI,SAAS,CACjB,cAAc,IAAI,CAAC,OAAO,+FAA+F,CAC1H,CAAC;IACJ,CAAC;IACD,IAAI,IAAI,CAAC,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,kBAAkB,EAAE,CAAC;QAC3D,MAAM,IAAI,SAAS,CACjB,cAAc,IAAI,CAAC,OAAO,QAAQ,IAAI,CAAC,SAAS,CAAC,MAAM,mCAAmC,IAAI,CAAC,MAAM,CAAC,kBAAkB,GAAG,CAC5H,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,gBAAgB,KAAK,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;QAChF,MAAM,IAAI,SAAS,CAAC,cAAc,IAAI,CAAC,OAAO,6BAA6B,CAAC,CAAC;IAC/E,CAAC;AACH,CAAC;AAED,MAAM,UAAU,mBAAmB,CACjC,QAAiC,EACjC,MAA+B,EAC/B,MAAM,GAAG,EAAE;IAEX,MAAM,KAAK,GAAgB,EAAE,CAAC;IAC9B,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;QAC/C,MAAM,QAAQ,GAAG,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC;QACnD,MAAM,aAAa,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;QACpC,MAAM,WAAW,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;QAChC,IACE,aAAa;YACb,OAAO,aAAa,KAAK,QAAQ;YACjC,CAAC,KAAK,CAAC,OAAO,CAAC,aAAa,CAAC;YAC7B,WAAW;YACX,OAAO,WAAW,KAAK,QAAQ;YAC/B,CAAC,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,EAC3B,CAAC;YACD,KAAK,CAAC,IAAI,CACR,GAAG,mBAAmB,CACpB,aAAwC,EACxC,WAAsC,EACtC,QAAQ,CACT,CACF,CAAC;QACJ,CAAC;aAAM,IACL,mBAAmB,CAAC,aAAa,CAAC,KAAK,mBAAmB,CAAC,WAAW,CAAC,EACvE,CAAC;YACD,KAAK,CAAC,IAAI,CAAC;gBACT,IAAI,EAAE,QAAQ;gBACd,QAAQ,EAAE,aAAa;gBACvB,MAAM,EAAE,WAAW;gBACnB,QAAQ,EAAE,UAAU;gBACpB,SAAS,EAAE,WAAW,QAAQ,0BAA0B;aACzD,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
|
package/dist/app-manifest.d.ts
CHANGED
|
@@ -2,6 +2,10 @@ export declare const APP_MANIFEST_FILES: readonly ["miosa.app.yml", "miosa.app.y
|
|
|
2
2
|
export interface MiosaAppManifest {
|
|
3
3
|
schema_version?: number;
|
|
4
4
|
name?: string;
|
|
5
|
+
organization?: string;
|
|
6
|
+
workspace?: string;
|
|
7
|
+
application?: string;
|
|
8
|
+
environment?: string;
|
|
5
9
|
template?: string;
|
|
6
10
|
framework?: string;
|
|
7
11
|
workdir?: string;
|
|
@@ -29,6 +33,52 @@ export interface MiosaAppManifest {
|
|
|
29
33
|
dependencies?: ProjectDependenciesManifest;
|
|
30
34
|
services?: Record<string, ProjectServiceManifest>;
|
|
31
35
|
requirements?: ProjectRequirementsManifest;
|
|
36
|
+
capabilities?: AppCapabilitiesManifest;
|
|
37
|
+
policy?: AppPolicyManifest;
|
|
38
|
+
}
|
|
39
|
+
export interface AppCapabilitiesManifest {
|
|
40
|
+
routes: Array<{
|
|
41
|
+
id: string;
|
|
42
|
+
path: string;
|
|
43
|
+
expected_status: number[];
|
|
44
|
+
body_contains?: string[];
|
|
45
|
+
content_type?: string;
|
|
46
|
+
required?: boolean;
|
|
47
|
+
}>;
|
|
48
|
+
secrets: Array<{
|
|
49
|
+
name: string;
|
|
50
|
+
required?: boolean;
|
|
51
|
+
}>;
|
|
52
|
+
database?: {
|
|
53
|
+
required: boolean;
|
|
54
|
+
health_path?: string;
|
|
55
|
+
migration?: {
|
|
56
|
+
required?: boolean;
|
|
57
|
+
command?: string;
|
|
58
|
+
compatibility?: "backward_compatible" | "incompatible";
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
connectors: Array<{
|
|
62
|
+
id: string;
|
|
63
|
+
required?: boolean;
|
|
64
|
+
}>;
|
|
65
|
+
jobs: Array<{
|
|
66
|
+
id: string;
|
|
67
|
+
required?: boolean;
|
|
68
|
+
}>;
|
|
69
|
+
business: Array<{
|
|
70
|
+
id: string;
|
|
71
|
+
path: string;
|
|
72
|
+
expected_status: number[];
|
|
73
|
+
body_contains?: string[];
|
|
74
|
+
required?: boolean;
|
|
75
|
+
}>;
|
|
76
|
+
}
|
|
77
|
+
export interface AppPolicyManifest {
|
|
78
|
+
approvals_required: number;
|
|
79
|
+
allowed_environments: string[];
|
|
80
|
+
require_immutable_release: boolean;
|
|
81
|
+
require_rollback_path: boolean;
|
|
32
82
|
}
|
|
33
83
|
export interface ProjectSandboxManifest {
|
|
34
84
|
name?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-manifest.d.ts","sourceRoot":"","sources":["../src/app-manifest.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,kBAAkB,gEAIrB,CAAC;AAEX,MAAM,WAAW,gBAAgB;IAC/B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IACzB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE;QACV,QAAQ,CAAC,EAAE,cAAc,CAAC;QAC1B,OAAO,CAAC,EAAE,cAAc,CAAC;QACzB,MAAM,CAAC,EAAE,cAAc,CAAC;QACxB,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,SAAS,CAAC,EAAE;QACV,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC;IACF,OAAO,CAAC,EAAE,sBAAsB,CAAC;IACjC,IAAI,CAAC,EAAE,mBAAmB,CAAC;IAC3B,YAAY,CAAC,EAAE,2BAA2B,CAAC;IAC3C,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC;IAClD,YAAY,CAAC,EAAE,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"app-manifest.d.ts","sourceRoot":"","sources":["../src/app-manifest.ts"],"names":[],"mappings":"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"}
|
package/dist/app-manifest.js
CHANGED
|
@@ -115,6 +115,29 @@ export function validateProjectManifest(manifest) {
|
|
|
115
115
|
usedPorts.set(service.port, name);
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
|
+
const capabilityIds = new Set();
|
|
119
|
+
for (const capability of [
|
|
120
|
+
...(manifest.capabilities?.routes ?? []),
|
|
121
|
+
...(manifest.capabilities?.business ?? []),
|
|
122
|
+
]) {
|
|
123
|
+
if (capabilityIds.has(capability.id)) {
|
|
124
|
+
issues.push({
|
|
125
|
+
code: "CAPABILITY_ID_DUPLICATE",
|
|
126
|
+
path: `capabilities.${capability.id}`,
|
|
127
|
+
message: `capability ID ${capability.id} is duplicated`,
|
|
128
|
+
fix: "Give every route and business capability a unique stable ID.",
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
capabilityIds.add(capability.id);
|
|
132
|
+
if (!capability.path.startsWith("/")) {
|
|
133
|
+
issues.push({
|
|
134
|
+
code: "CAPABILITY_PATH_INVALID",
|
|
135
|
+
path: `capabilities.${capability.id}.path`,
|
|
136
|
+
message: "capability probe paths must start with /",
|
|
137
|
+
fix: `Set the ${capability.id} probe to an absolute application path.`,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
118
141
|
return issues;
|
|
119
142
|
}
|
|
120
143
|
export function manifestProbePath(manifest) {
|
|
@@ -156,6 +179,8 @@ function normalizeManifest(value) {
|
|
|
156
179
|
const dependencies = recordValue(row["dependencies"]);
|
|
157
180
|
const requirements = recordValue(row["requirements"]);
|
|
158
181
|
const services = normalizeServices(recordValue(row["services"]));
|
|
182
|
+
const capabilities = normalizeCapabilities(recordValue(row["capabilities"]));
|
|
183
|
+
const policy = normalizePolicy(recordValue(row["policy"]));
|
|
159
184
|
const workdir = stringValue(sandbox?.["workdir"] ?? row["workdir"] ?? row["workspace"] ?? row["root"]) ?? "/workspace";
|
|
160
185
|
const install = dependencies?.["install"] === false || row["install"] === false
|
|
161
186
|
? false
|
|
@@ -163,6 +188,10 @@ function normalizeManifest(value) {
|
|
|
163
188
|
return {
|
|
164
189
|
schema_version: numberValue(row["schema_version"]),
|
|
165
190
|
name: stringValue(row["name"]),
|
|
191
|
+
organization: stringValue(row["organization"]),
|
|
192
|
+
workspace: stringValue(row["workspace"]),
|
|
193
|
+
application: stringValue(row["application"]),
|
|
194
|
+
environment: stringValue(row["environment"]),
|
|
166
195
|
template: stringValue(row["template"] ?? row["template_id"]),
|
|
167
196
|
framework: stringValue(row["framework"]),
|
|
168
197
|
workdir,
|
|
@@ -198,6 +227,83 @@ function normalizeManifest(value) {
|
|
|
198
227
|
secrets: stringArray(requirements?.["secrets"]),
|
|
199
228
|
database: requirements?.["database"] === true,
|
|
200
229
|
},
|
|
230
|
+
capabilities,
|
|
231
|
+
policy,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
function normalizeCapabilities(value) {
|
|
235
|
+
if (!value)
|
|
236
|
+
return undefined;
|
|
237
|
+
const database = recordValue(value["database"]);
|
|
238
|
+
const migration = recordValue(database?.["migration"]);
|
|
239
|
+
return {
|
|
240
|
+
routes: normalizeProbeCapabilities(value["routes"]),
|
|
241
|
+
secrets: normalizeNamedCapabilities(value["secrets"], "name"),
|
|
242
|
+
database: database
|
|
243
|
+
? {
|
|
244
|
+
required: database["required"] === true,
|
|
245
|
+
health_path: stringValue(database["health_path"]),
|
|
246
|
+
migration: migration
|
|
247
|
+
? {
|
|
248
|
+
required: migration["required"] === true,
|
|
249
|
+
command: stringValue(migration["command"]),
|
|
250
|
+
compatibility: migration["compatibility"] === "backward_compatible" ||
|
|
251
|
+
migration["compatibility"] === "incompatible"
|
|
252
|
+
? migration["compatibility"]
|
|
253
|
+
: undefined,
|
|
254
|
+
}
|
|
255
|
+
: undefined,
|
|
256
|
+
}
|
|
257
|
+
: undefined,
|
|
258
|
+
connectors: normalizeNamedCapabilities(value["connectors"], "id"),
|
|
259
|
+
jobs: normalizeNamedCapabilities(value["jobs"], "id"),
|
|
260
|
+
business: normalizeProbeCapabilities(value["business"]),
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
function normalizeProbeCapabilities(value) {
|
|
264
|
+
if (!Array.isArray(value))
|
|
265
|
+
return [];
|
|
266
|
+
return value.flatMap((candidate) => {
|
|
267
|
+
const row = recordValue(candidate);
|
|
268
|
+
const id = stringValue(row?.["id"]);
|
|
269
|
+
const routePath = stringValue(row?.["path"]);
|
|
270
|
+
const statuses = Array.isArray(row?.["expected_status"])
|
|
271
|
+
? row["expected_status"].filter((status) => typeof status === "number" && Number.isInteger(status))
|
|
272
|
+
: [];
|
|
273
|
+
if (!id || !routePath || statuses.length === 0)
|
|
274
|
+
return [];
|
|
275
|
+
return [{
|
|
276
|
+
id,
|
|
277
|
+
path: routePath,
|
|
278
|
+
expected_status: statuses,
|
|
279
|
+
body_contains: stringArray(row?.["body_contains"]),
|
|
280
|
+
content_type: stringValue(row?.["content_type"]),
|
|
281
|
+
required: row?.["required"] !== false,
|
|
282
|
+
}];
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
function normalizeNamedCapabilities(value, key) {
|
|
286
|
+
if (!Array.isArray(value))
|
|
287
|
+
return [];
|
|
288
|
+
return value.flatMap((candidate) => {
|
|
289
|
+
if (typeof candidate === "string" && candidate.trim()) {
|
|
290
|
+
return [{ [key]: candidate.trim(), required: true }];
|
|
291
|
+
}
|
|
292
|
+
const row = recordValue(candidate);
|
|
293
|
+
const name = stringValue(row?.[key]);
|
|
294
|
+
if (!name)
|
|
295
|
+
return [];
|
|
296
|
+
return [{ [key]: name, required: row?.["required"] !== false }];
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
function normalizePolicy(value) {
|
|
300
|
+
if (!value)
|
|
301
|
+
return undefined;
|
|
302
|
+
return {
|
|
303
|
+
approvals_required: numberValue(value["approvals_required"]) ?? 1,
|
|
304
|
+
allowed_environments: stringArray(value["allowed_environments"]),
|
|
305
|
+
require_immutable_release: value["require_immutable_release"] !== false,
|
|
306
|
+
require_rollback_path: value["require_rollback_path"] !== false,
|
|
201
307
|
};
|
|
202
308
|
}
|
|
203
309
|
function normalizeServices(value) {
|