@pattform/engine 0.0.0-stage → 0.1.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.
package/README.md CHANGED
@@ -1,3 +1,35 @@
1
- # Temporary Holding Version
1
+ # Pattform Engine
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ `pattform` turns an app repo's `deploy/*.yaml` specs into Pulumi stacks using Pattform capabilities (ADR-001, "Design: Capabilities and Engine"). It holds no infrastructure logic of its own: each spec names a capability and pins its version, and the engine installs that version and runs it.
4
+
5
+ ```bash
6
+ pattform validate # schemas + secret keys; no cloud credentials needed
7
+ pattform preview # validate, then pulumi preview per spec
8
+ pattform up # validate, then pulumi up per spec (Client specs first)
9
+ pattform drift # refresh + expect no changes; non-zero exit on drift
10
+ ```
11
+
12
+ Options: `--dir <repo root>`, `--kind <Kind>`, `--exclude-kind <Kind>`, and `--capabilities-dir <path>` to install capabilities from a local, built `pattform-capabilities` checkout instead of npm.
13
+
14
+ ## What one run does, per spec
15
+
16
+ 1. **Load** every YAML document in `deploy/` except `*.sops.yaml`, and check the envelope: `apiVersion: platform.pattform.io/<version>`, PascalCase `kind`, DNS-1123 `metadata.name`, an exact `platform.pattform.io/capability-version`, no committed `status`.
17
+ 2. **Install** `@pattform/capability-<kind>@<version>` into `.pattform/work/<kind>.<namespace>.<name>/` with a generated `Pulumi.yaml` and a one-line program.
18
+ 3. **Validate** the spec against the capability's schema for its `apiVersion`, rejecting unknown fields, and check every secret the capability will read exists in the referenced SOPS file (key names are plaintext, so no KMS is needed).
19
+ 4. **Run** `preview`, `up` or `drift` through the Pulumi Automation API against `s3://pattform-pulumi-state` with the `awskms://alias/pattform-pulumi` secrets provider. The spec, metadata and repo go in as stack config; only the referenced secrets go in, as secret config.
20
+ 5. **Report** `status.conditions` (`Ready`, `Synced`) and outputs to `.pattform/status.json` and, in GitHub Actions, the job summary.
21
+
22
+ ## Stack names
23
+
24
+ - `organization/<kind>/<namespace>.<name>` for namespaced kinds, e.g. `organization/landing-site/tazland.tazland-landing`
25
+ - `organization/client/<name>` for clients
26
+ - `platform.pattform.io/stack: <project>/<stack>` adopts an existing stack instead
27
+
28
+ ## Development
29
+
30
+ ```bash
31
+ npm install
32
+ npm test # unit tests + an end-to-end validate of test/fixtures/tazland
33
+ ```
34
+
35
+ The end-to-end test installs capabilities from `../pattform-capabilities`, which must be built. The fixture's `secrets.sops.yaml` holds dummy values encrypted with the real KMS key.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
36
+ Object.defineProperty(exports, "__esModule", { value: true });
37
+ const path = __importStar(require("node:path"));
38
+ const engine_1 = require("./engine");
39
+ const spec_1 = require("./spec");
40
+ const USAGE = `Usage: pattform <validate|preview|up|drift> [options]
41
+
42
+ validate check every spec in deploy/ against its capability schema and
43
+ check referenced secrets exist (no cloud credentials needed)
44
+ preview validate, then pulumi preview each spec
45
+ up validate, then pulumi up each spec (Client specs first)
46
+ drift pulumi preview --refresh --expect-no-changes each spec;
47
+ exits non-zero if anything drifted
48
+
49
+ Options:
50
+ --dir <path> repo root (default: current directory)
51
+ --kind <Kind> only specs of this kind (repeatable)
52
+ --exclude-kind <Kind> skip specs of this kind (repeatable)
53
+ --capabilities-dir <path> install capabilities from a local, built
54
+ pattform-capabilities checkout instead of npm
55
+ `;
56
+ function parseArgs(argv) {
57
+ const [command, ...rest] = argv;
58
+ const options = { root: process.cwd(), kinds: [], excludeKinds: [], capabilitiesDir: undefined };
59
+ for (let i = 0; i < rest.length; i++) {
60
+ const flag = rest[i];
61
+ const value = rest[i + 1];
62
+ if (value === undefined)
63
+ throw new spec_1.SpecError(`${flag} needs a value`);
64
+ switch (flag) {
65
+ case "--dir":
66
+ options.root = path.resolve(value);
67
+ break;
68
+ case "--kind":
69
+ options.kinds.push(value);
70
+ break;
71
+ case "--exclude-kind":
72
+ options.excludeKinds.push(value);
73
+ break;
74
+ case "--capabilities-dir":
75
+ options.capabilitiesDir = path.resolve(value);
76
+ break;
77
+ default: throw new spec_1.SpecError(`unknown option ${flag}`);
78
+ }
79
+ i++;
80
+ }
81
+ return { command, options };
82
+ }
83
+ async function main() {
84
+ const { command, options } = parseArgs(process.argv.slice(2));
85
+ if (!["validate", "preview", "up", "drift"].includes(command)) {
86
+ process.stdout.write(USAGE);
87
+ process.exit(command ? 2 : 0);
88
+ }
89
+ const reports = await (0, engine_1.execute)(command, {
90
+ ...options,
91
+ deployDir: path.join(options.root, "deploy"),
92
+ });
93
+ (0, engine_1.writeReports)(options.root, command, reports);
94
+ const failed = reports.filter(r => r.status.conditions.some(c => c.status === "False"));
95
+ for (const report of failed) {
96
+ const condition = report.status.conditions.find(c => c.status === "False");
97
+ console.error(`FAILED ${report.id}: ${condition.reason}: ${condition.message}`);
98
+ }
99
+ if (failed.length)
100
+ process.exit(1);
101
+ }
102
+ main().catch(error => {
103
+ console.error(error instanceof spec_1.SpecError ? `invalid: ${error.message}` : error);
104
+ process.exit(error instanceof spec_1.SpecError ? 2 : 1);
105
+ });
106
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ import { SpecDocument } from "./spec";
2
+ import { SecretRef } from "./secrets";
3
+ import { LoadedCapability, WorkspaceOptions } from "./workspace";
4
+ import { Operation } from "./run";
5
+ export type Command = "validate" | Operation;
6
+ export interface EngineOptions extends WorkspaceOptions {
7
+ deployDir: string;
8
+ kinds?: string[];
9
+ excludeKinds?: string[];
10
+ awsRegion?: string;
11
+ log?: (line: string) => void;
12
+ }
13
+ interface Planned {
14
+ spec: SpecDocument;
15
+ capability: LoadedCapability;
16
+ workDir: string;
17
+ project: string;
18
+ stack: string;
19
+ refs: SecretRef[];
20
+ }
21
+ export interface StackReport {
22
+ id: string;
23
+ stack: string;
24
+ status: {
25
+ conditions: {
26
+ type: string;
27
+ status: "True" | "False";
28
+ reason: string;
29
+ message: string;
30
+ lastTransitionTime: string;
31
+ }[];
32
+ outputs?: {
33
+ [key: string]: unknown;
34
+ };
35
+ };
36
+ changes?: {
37
+ [op: string]: number;
38
+ };
39
+ }
40
+ export interface RepoInfo {
41
+ owner: string;
42
+ name: string;
43
+ url: string;
44
+ branch: string;
45
+ }
46
+ export declare function detectRepo(root: string): RepoInfo;
47
+ export declare function plan(options: EngineOptions): Planned[];
48
+ export declare function execute(command: Command, options: EngineOptions): Promise<StackReport[]>;
49
+ export declare function writeReports(root: string, command: Command, reports: StackReport[]): void;
50
+ export {};
@@ -0,0 +1,195 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.detectRepo = detectRepo;
37
+ exports.plan = plan;
38
+ exports.execute = execute;
39
+ exports.writeReports = writeReports;
40
+ const fs = __importStar(require("node:fs"));
41
+ const path = __importStar(require("node:path"));
42
+ const node_child_process_1 = require("node:child_process");
43
+ const spec_1 = require("./spec");
44
+ const schema_1 = require("./schema");
45
+ const secrets_1 = require("./secrets");
46
+ const workspace_1 = require("./workspace");
47
+ const run_1 = require("./run");
48
+ // GitHub Actions sets GITHUB_REPOSITORY; locally, read the origin remote.
49
+ function detectRepo(root) {
50
+ let slug = process.env.GITHUB_REPOSITORY;
51
+ if (!slug) {
52
+ const remote = (0, node_child_process_1.execFileSync)("git", ["-C", root, "remote", "get-url", "origin"], { encoding: "utf8" }).trim();
53
+ const match = /github\.com[:/]([^/]+)\/(.+?)(\.git)?$/.exec(remote);
54
+ if (!match)
55
+ throw new Error(`cannot read the GitHub repo from origin remote ${remote}`);
56
+ slug = `${match[1]}/${match[2]}`;
57
+ }
58
+ const [owner, name] = slug.split("/");
59
+ const branch = process.env.GITHUB_HEAD_REF || process.env.GITHUB_REF_NAME || "main";
60
+ return { owner, name, url: `https://github.com/${owner}/${name}`, branch };
61
+ }
62
+ function selected(spec, options) {
63
+ if (options.kinds?.length && !options.kinds.includes(spec.kind))
64
+ return false;
65
+ if (options.excludeKinds?.includes(spec.kind))
66
+ return false;
67
+ return true;
68
+ }
69
+ // Load, install and validate every selected spec. Needs npm but no cloud
70
+ // credentials, so `validate` runs safely on any PR.
71
+ function plan(options) {
72
+ const log = options.log ?? console.log;
73
+ const specs = (0, spec_1.loadSpecs)(options.deployDir).filter(spec => selected(spec, options));
74
+ const planned = [];
75
+ for (const spec of specs) {
76
+ const owner = `${spec.file} ${(0, spec_1.specId)(spec)}`;
77
+ const version = (0, spec_1.capabilityVersion)(spec);
78
+ const dirName = (0, spec_1.specId)(spec).replace(/\//g, ".");
79
+ const workDir = (0, workspace_1.prepareWorkspace)(options, dirName, spec.kind, version);
80
+ const capability = (0, workspace_1.loadCapability)(workDir, spec.kind);
81
+ if (capability.scope === "Namespaced" && !spec.metadata.namespace) {
82
+ throw new spec_1.SpecError(`${owner}: ${spec.kind} is namespaced, so metadata.namespace (the client) is required`);
83
+ }
84
+ if (capability.scope === "Cluster" && spec.metadata.namespace) {
85
+ throw new spec_1.SpecError(`${owner}: ${spec.kind} is cluster-scoped, so it takes no metadata.namespace`);
86
+ }
87
+ const { project, stack } = (0, spec_1.stackName)(spec, capability.scope);
88
+ (0, workspace_1.writeProject)(workDir, project, spec.kind, version);
89
+ const schema = capability.schemas[spec.apiVersion];
90
+ if (!schema) {
91
+ throw new spec_1.SpecError(`${owner}: ${spec.kind}@${version} has no schema for ${spec.apiVersion} (has ${Object.keys(capability.schemas).join(", ")})`);
92
+ }
93
+ (0, schema_1.validateSpec)(schema, spec.spec, owner);
94
+ const refs = capability.secretRefs ? capability.secretRefs(spec.spec) : (0, secrets_1.collectExplicitRefs)(spec.spec);
95
+ (0, secrets_1.checkRefs)(options.deployDir, refs, owner);
96
+ log(`ok ${(0, spec_1.specId)(spec)} ${spec.kind}@${version} -> organization/${project}/${stack}`);
97
+ planned.push({ spec, capability, workDir, project, stack, refs });
98
+ }
99
+ // Cluster-scoped kinds (Client) first: namespaced specs read the deploy
100
+ // identities they publish.
101
+ return planned.sort((a, b) => (a.capability.scope === "Cluster" ? 0 : 1) - (b.capability.scope === "Cluster" ? 0 : 1));
102
+ }
103
+ function conditions(ok, operation, result, error) {
104
+ const now = new Date().toISOString();
105
+ if (!ok) {
106
+ return [
107
+ { type: "Synced", status: "False", reason: "RunFailed", message: String(error).slice(0, 500), lastTransitionTime: now },
108
+ { type: "Ready", status: "False", reason: "RunFailed", message: `${operation} failed`, lastTransitionTime: now },
109
+ ];
110
+ }
111
+ if (operation === "drift") {
112
+ return [{
113
+ type: "Synced", status: result?.drifted ? "False" : "True",
114
+ reason: result?.drifted ? "DriftDetected" : "InSync",
115
+ message: result?.drifted ? "live resources differ from the spec" : "no drift", lastTransitionTime: now,
116
+ }];
117
+ }
118
+ if (operation === "preview") {
119
+ return [{ type: "Synced", status: "True", reason: "Previewed", message: JSON.stringify(result?.changes ?? {}), lastTransitionTime: now }];
120
+ }
121
+ return [
122
+ { type: "Synced", status: "True", reason: "Applied", message: JSON.stringify(result?.changes ?? {}), lastTransitionTime: now },
123
+ { type: "Ready", status: "True", reason: "Deployed", message: "stack is up to date", lastTransitionTime: now },
124
+ ];
125
+ }
126
+ async function execute(command, options) {
127
+ const log = options.log ?? console.log;
128
+ const planned = plan(options);
129
+ if (command === "validate") {
130
+ return planned.map(p => ({
131
+ id: (0, spec_1.specId)(p.spec),
132
+ stack: `organization/${p.project}/${p.stack}`,
133
+ status: { conditions: [] },
134
+ }));
135
+ }
136
+ const repo = detectRepo(options.root);
137
+ const secrets = new secrets_1.SecretStore(options.deployDir);
138
+ const reports = [];
139
+ for (const p of planned) {
140
+ const id = (0, spec_1.specId)(p.spec);
141
+ log(`\n=== ${command} ${id} (organization/${p.project}/${p.stack}) ===`);
142
+ let result;
143
+ let error;
144
+ try {
145
+ result = await (0, run_1.runStack)(command, {
146
+ workDir: p.workDir,
147
+ project: p.project,
148
+ stack: p.stack,
149
+ config: {
150
+ "aws:region": options.awsRegion ?? "ap-southeast-2",
151
+ "pattform:apiVersion": p.spec.apiVersion,
152
+ "pattform:metadata": p.spec.metadata,
153
+ "pattform:spec": p.spec.spec,
154
+ "pattform:repo": repo,
155
+ },
156
+ secretConfig: { "pattform:secrets": secrets.resolve(p.refs) },
157
+ onOutput: line => process.stdout.write(line),
158
+ });
159
+ }
160
+ catch (e) {
161
+ error = e;
162
+ }
163
+ reports.push({
164
+ id,
165
+ stack: `organization/${p.project}/${p.stack}`,
166
+ status: { conditions: conditions(!error, command, result, error), outputs: result?.outputs },
167
+ changes: result?.changes,
168
+ });
169
+ // Stop at the first failed up: later specs may depend on this one.
170
+ if (error && command === "up")
171
+ break;
172
+ }
173
+ return reports;
174
+ }
175
+ function writeReports(root, command, reports) {
176
+ const dir = path.join(root, ".pattform");
177
+ fs.mkdirSync(dir, { recursive: true });
178
+ fs.writeFileSync(path.join(dir, "status.json"), JSON.stringify({ command, reports }, null, 2));
179
+ const summary = process.env.GITHUB_STEP_SUMMARY;
180
+ if (summary) {
181
+ const rows = reports.map(r => {
182
+ const failed = r.status.conditions.find(c => c.status === "False");
183
+ return `| ${r.id} | \`${r.stack}\` | ${failed ? `${failed.reason}` : "OK"} | ${r.changes ? JSON.stringify(r.changes) : ""} |`;
184
+ });
185
+ fs.appendFileSync(summary, [
186
+ `### pattform ${command}`,
187
+ "",
188
+ "| Spec | Stack | Result | Changes |",
189
+ "| --- | --- | --- | --- |",
190
+ ...rows,
191
+ "",
192
+ ].join("\n"));
193
+ }
194
+ }
195
+ //# sourceMappingURL=engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.js","sourceRoot":"","sources":["../../src/engine.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export { execute, plan, detectRepo, writeReports } from "./engine";
2
+ export type { Command, EngineOptions, StackReport, RepoInfo } from "./engine";
3
+ export { loadSpecs, stackName, capabilityPackage, SpecError } from "./spec";
4
+ export { validateSpec, strict } from "./schema";
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.strict = exports.validateSpec = exports.SpecError = exports.capabilityPackage = exports.stackName = exports.loadSpecs = exports.writeReports = exports.detectRepo = exports.plan = exports.execute = void 0;
4
+ var engine_1 = require("./engine");
5
+ Object.defineProperty(exports, "execute", { enumerable: true, get: function () { return engine_1.execute; } });
6
+ Object.defineProperty(exports, "plan", { enumerable: true, get: function () { return engine_1.plan; } });
7
+ Object.defineProperty(exports, "detectRepo", { enumerable: true, get: function () { return engine_1.detectRepo; } });
8
+ Object.defineProperty(exports, "writeReports", { enumerable: true, get: function () { return engine_1.writeReports; } });
9
+ var spec_1 = require("./spec");
10
+ Object.defineProperty(exports, "loadSpecs", { enumerable: true, get: function () { return spec_1.loadSpecs; } });
11
+ Object.defineProperty(exports, "stackName", { enumerable: true, get: function () { return spec_1.stackName; } });
12
+ Object.defineProperty(exports, "capabilityPackage", { enumerable: true, get: function () { return spec_1.capabilityPackage; } });
13
+ Object.defineProperty(exports, "SpecError", { enumerable: true, get: function () { return spec_1.SpecError; } });
14
+ var schema_1 = require("./schema");
15
+ Object.defineProperty(exports, "validateSpec", { enumerable: true, get: function () { return schema_1.validateSpec; } });
16
+ Object.defineProperty(exports, "strict", { enumerable: true, get: function () { return schema_1.strict; } });
17
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;AAAA,mCAAmE;AAA1D,iGAAA,OAAO,OAAA;AAAE,8FAAA,IAAI,OAAA;AAAE,oGAAA,UAAU,OAAA;AAAE,sGAAA,YAAY,OAAA;AAEhD,+BAA4E;AAAnE,iGAAA,SAAS,OAAA;AAAE,iGAAA,SAAS,OAAA;AAAE,yGAAA,iBAAiB,OAAA;AAAE,iGAAA,SAAS,OAAA;AAC3D,mCAAgD;AAAvC,sGAAA,YAAY,OAAA;AAAE,gGAAA,MAAM,OAAA"}
@@ -0,0 +1,23 @@
1
+ export type Operation = "preview" | "up" | "drift";
2
+ export interface StackRun {
3
+ workDir: string;
4
+ project: string;
5
+ stack: string;
6
+ config: {
7
+ [key: string]: unknown;
8
+ };
9
+ secretConfig: {
10
+ [key: string]: unknown;
11
+ };
12
+ onOutput: (line: string) => void;
13
+ }
14
+ export interface RunResult {
15
+ changes: {
16
+ [op: string]: number;
17
+ };
18
+ outputs?: {
19
+ [key: string]: unknown;
20
+ };
21
+ drifted?: boolean;
22
+ }
23
+ export declare function runStack(operation: Operation, run: StackRun): Promise<RunResult>;
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runStack = runStack;
4
+ const automation_1 = require("@pulumi/pulumi/automation");
5
+ const workspace_1 = require("./workspace");
6
+ function toConfigMap(values, secret) {
7
+ const map = {};
8
+ for (const [key, value] of Object.entries(values)) {
9
+ map[key] = { value: typeof value === "string" ? value : JSON.stringify(value), secret };
10
+ }
11
+ return map;
12
+ }
13
+ function plainOutputs(outputs) {
14
+ const result = {};
15
+ for (const [key, output] of Object.entries(outputs)) {
16
+ result[key] = output.secret ? "[secret]" : output.value;
17
+ }
18
+ return result;
19
+ }
20
+ async function runStack(operation, run) {
21
+ const stack = await automation_1.LocalWorkspace.createOrSelectStack({ stackName: `organization/${run.project}/${run.stack}`, workDir: run.workDir }, { secretsProvider: workspace_1.SECRETS_PROVIDER });
22
+ await stack.setAllConfig({
23
+ ...toConfigMap(run.config, false),
24
+ ...toConfigMap(run.secretConfig, true),
25
+ });
26
+ switch (operation) {
27
+ case "preview": {
28
+ const result = await stack.preview({ diff: true, onOutput: run.onOutput });
29
+ return { changes: result.changeSummary };
30
+ }
31
+ case "drift": {
32
+ try {
33
+ const result = await stack.preview({ refresh: true, expectNoChanges: true, diff: true, onOutput: run.onOutput });
34
+ return { changes: result.changeSummary, drifted: false };
35
+ }
36
+ catch (error) {
37
+ // expectNoChanges fails the preview when anything would change;
38
+ // any other failure (auth, a broken program) is a real error.
39
+ if (/no changes were expected/i.test(String(error))) {
40
+ return { changes: {}, drifted: true };
41
+ }
42
+ throw error;
43
+ }
44
+ }
45
+ case "up": {
46
+ const result = await stack.up({ diff: true, onOutput: run.onOutput });
47
+ return {
48
+ changes: (result.summary.resourceChanges ?? {}),
49
+ outputs: plainOutputs(result.outputs),
50
+ };
51
+ }
52
+ }
53
+ }
54
+ //# sourceMappingURL=run.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/run.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function strict(node: any): any;
2
+ export declare function validateSpec(schema: object, spec: unknown, owner: string): void;
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.strict = strict;
7
+ exports.validateSpec = validateSpec;
8
+ const ajv_1 = __importDefault(require("ajv"));
9
+ const ajv_formats_1 = __importDefault(require("ajv-formats"));
10
+ const spec_1 = require("./spec");
11
+ // Capability schemas are CRD-compatible, which rules out additionalProperties
12
+ // next to properties. Unknown fields are still rejected here, the way
13
+ // Kubernetes strict field validation would, by adding it before compiling.
14
+ function strict(node) {
15
+ if (Array.isArray(node))
16
+ return node.map(strict);
17
+ if (node && typeof node === "object") {
18
+ const out = {};
19
+ for (const [key, value] of Object.entries(node))
20
+ out[key] = strict(value);
21
+ if (out.type === "object" && out.properties && out.additionalProperties === undefined) {
22
+ out.additionalProperties = false;
23
+ }
24
+ return out;
25
+ }
26
+ return node;
27
+ }
28
+ const ajv = new ajv_1.default({ allErrors: true, strict: false });
29
+ (0, ajv_formats_1.default)(ajv);
30
+ const compiled = new WeakMap();
31
+ function validateSpec(schema, spec, owner) {
32
+ let validate = compiled.get(schema);
33
+ if (!validate) {
34
+ validate = ajv.compile(strict(schema));
35
+ compiled.set(schema, validate);
36
+ }
37
+ if (!validate(spec)) {
38
+ const problems = (validate.errors ?? []).map(error => {
39
+ const at = error.instancePath ? `spec${error.instancePath.replace(/\//g, ".")}` : "spec";
40
+ const extra = error.keyword === "additionalProperties"
41
+ ? ` (${error.params.additionalProperty})`
42
+ : "";
43
+ return `${at} ${error.message}${extra}`;
44
+ });
45
+ throw new spec_1.SpecError(`${owner}: ${problems.join("; ")}`);
46
+ }
47
+ }
48
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/schema.ts"],"names":[],"mappings":";;;;;AAOA,wBAWC;AAMD,oCAgBC;AAxCD,8CAA4C;AAC5C,8DAAqC;AACrC,iCAAmC;AAEnC,8EAA8E;AAC9E,sEAAsE;AACtE,2EAA2E;AAC3E,SAAgB,MAAM,CAAC,IAAS;IAC5B,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACjD,IAAI,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;QACnC,MAAM,GAAG,GAAQ,EAAE,CAAC;QACpB,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC;YAAE,GAAG,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;QAC1E,IAAI,GAAG,CAAC,IAAI,KAAK,QAAQ,IAAI,GAAG,CAAC,UAAU,IAAI,GAAG,CAAC,oBAAoB,KAAK,SAAS,EAAE,CAAC;YACpF,GAAG,CAAC,oBAAoB,GAAG,KAAK,CAAC;QACrC,CAAC;QACD,OAAO,GAAG,CAAC;IACf,CAAC;IACD,OAAO,IAAI,CAAC;AAChB,CAAC;AAED,MAAM,GAAG,GAAG,IAAI,aAAG,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;AACxD,IAAA,qBAAU,EAAC,GAAG,CAAC,CAAC;AAChB,MAAM,QAAQ,GAAG,IAAI,OAAO,EAA4B,CAAC;AAEzD,SAAgB,YAAY,CAAC,MAAc,EAAE,IAAa,EAAE,KAAa;IACrE,IAAI,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACpC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACZ,QAAQ,GAAG,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;QACvC,QAAQ,CAAC,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACnC,CAAC;IACD,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QAClB,MAAM,QAAQ,GAAG,CAAC,QAAQ,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE;YACjD,MAAM,EAAE,GAAG,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC,OAAO,KAAK,CAAC,YAAY,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;YACzF,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,KAAK,sBAAsB;gBAClD,CAAC,CAAC,KAAM,KAAK,CAAC,MAAc,CAAC,kBAAkB,GAAG;gBAClD,CAAC,CAAC,EAAE,CAAC;YACT,OAAO,GAAG,EAAE,IAAI,KAAK,CAAC,OAAO,GAAG,KAAK,EAAE,CAAC;QAC5C,CAAC,CAAC,CAAC;QACH,MAAM,IAAI,gBAAS,CAAC,GAAG,KAAK,KAAK,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC5D,CAAC;AACL,CAAC"}
@@ -0,0 +1,16 @@
1
+ export interface SecretRef {
2
+ name: string;
3
+ key: string;
4
+ }
5
+ export declare function refId(ref: SecretRef): string;
6
+ export declare function collectExplicitRefs(value: unknown, refs?: SecretRef[]): SecretRef[];
7
+ export declare function checkRefs(deployDir: string, refs: SecretRef[], owner: string): void;
8
+ export declare class SecretStore {
9
+ private readonly deployDir;
10
+ private readonly files;
11
+ constructor(deployDir: string);
12
+ private decrypt;
13
+ resolve(refs: SecretRef[]): {
14
+ [id: string]: string;
15
+ };
16
+ }
@@ -0,0 +1,125 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.SecretStore = void 0;
37
+ exports.refId = refId;
38
+ exports.collectExplicitRefs = collectExplicitRefs;
39
+ exports.checkRefs = checkRefs;
40
+ const fs = __importStar(require("node:fs"));
41
+ const path = __importStar(require("node:path"));
42
+ const node_child_process_1 = require("node:child_process");
43
+ const yaml_1 = require("yaml");
44
+ const spec_1 = require("./spec");
45
+ function refId(ref) {
46
+ return `${ref.name}/${ref.key}`;
47
+ }
48
+ // Fallback when a capability doesn't declare secretRefs(): every object
49
+ // shaped {name, key} under a property ending in "Ref".
50
+ function collectExplicitRefs(value, refs = []) {
51
+ if (Array.isArray(value)) {
52
+ value.forEach(item => collectExplicitRefs(item, refs));
53
+ }
54
+ else if (value && typeof value === "object") {
55
+ for (const [key, child] of Object.entries(value)) {
56
+ if (key.endsWith("Ref") && child && typeof child === "object"
57
+ && typeof child.name === "string" && typeof child.key === "string") {
58
+ refs.push({ name: child.name, key: child.key });
59
+ }
60
+ else {
61
+ collectExplicitRefs(child, refs);
62
+ }
63
+ }
64
+ }
65
+ return refs;
66
+ }
67
+ function secretsFile(deployDir, name) {
68
+ return path.join(deployDir, `${name}.sops.yaml`);
69
+ }
70
+ function checkRefs(deployDir, refs, owner) {
71
+ const keysByFile = new Map();
72
+ for (const ref of refs) {
73
+ let keys = keysByFile.get(ref.name);
74
+ if (!keys) {
75
+ const file = secretsFile(deployDir, ref.name);
76
+ if (!fs.existsSync(file)) {
77
+ throw new spec_1.SpecError(`${owner}: secret ${refId(ref)} needs ${path.basename(file)} in deploy/`);
78
+ }
79
+ const doc = (0, yaml_1.parse)(fs.readFileSync(file, "utf8")) ?? {};
80
+ if (!doc.sops) {
81
+ throw new spec_1.SpecError(`${owner}: ${path.basename(file)} is not SOPS-encrypted; never commit plaintext secrets`);
82
+ }
83
+ keys = new Set(Object.keys(doc).filter(key => key !== "sops"));
84
+ keysByFile.set(ref.name, keys);
85
+ }
86
+ if (!keys.has(ref.key)) {
87
+ throw new spec_1.SpecError(`${owner}: secret ${refId(ref)} is missing from ${ref.name}.sops.yaml (add it with: sops ${ref.name}.sops.yaml)`);
88
+ }
89
+ }
90
+ }
91
+ // Decrypts each referenced file once per run. Uses the ambient AWS
92
+ // credentials (the job's OIDC role, or `aws login` locally).
93
+ class SecretStore {
94
+ deployDir;
95
+ files = new Map();
96
+ constructor(deployDir) {
97
+ this.deployDir = deployDir;
98
+ }
99
+ decrypt(name) {
100
+ let values = this.files.get(name);
101
+ if (!values) {
102
+ const output = (0, node_child_process_1.execFileSync)("sops", ["decrypt", "--output-type", "json", secretsFile(this.deployDir, name)], {
103
+ encoding: "utf8",
104
+ stdio: ["ignore", "pipe", "pipe"],
105
+ });
106
+ values = JSON.parse(output);
107
+ this.files.set(name, values);
108
+ }
109
+ return values;
110
+ }
111
+ // Only the refs a stack uses, keyed "<file>/<key>".
112
+ resolve(refs) {
113
+ const resolved = {};
114
+ for (const ref of refs) {
115
+ const value = this.decrypt(ref.name)[ref.key];
116
+ if (typeof value !== "string") {
117
+ throw new spec_1.SpecError(`secret ${refId(ref)} must be a string`);
118
+ }
119
+ resolved[refId(ref)] = value;
120
+ }
121
+ return resolved;
122
+ }
123
+ }
124
+ exports.SecretStore = SecretStore;
125
+ //# sourceMappingURL=secrets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secrets.js","sourceRoot":"","sources":["../../src/secrets.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgBA,sBAEC;AAID,kDAcC;AAMD,8BAoBC;AA9DD,4CAA8B;AAC9B,gDAAkC;AAClC,2DAAkD;AAClD,+BAA6B;AAC7B,iCAAmC;AAYnC,SAAgB,KAAK,CAAC,GAAc;IAChC,OAAO,GAAG,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,GAAG,EAAE,CAAC;AACpC,CAAC;AAED,wEAAwE;AACxE,uDAAuD;AACvD,SAAgB,mBAAmB,CAAC,KAAc,EAAE,OAAoB,EAAE;IACtE,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACvB,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,mBAAmB,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;IAC3D,CAAC;SAAM,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC5C,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YAC/C,IAAI,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;mBACtD,OAAQ,KAAa,CAAC,IAAI,KAAK,QAAQ,IAAI,OAAQ,KAAa,CAAC,GAAG,KAAK,QAAQ,EAAE,CAAC;gBACvF,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAG,KAAa,CAAC,IAAI,EAAE,GAAG,EAAG,KAAa,CAAC,GAAG,EAAE,CAAC,CAAC;YACtE,CAAC;iBAAM,CAAC;gBACJ,mBAAmB,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;YACrC,CAAC;QACL,CAAC;IACL,CAAC;IACD,OAAO,IAAI,CAAC;AAChB,CAAC;AAED,SAAS,WAAW,CAAC,SAAiB,EAAE,IAAY;IAChD,OAAO,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,IAAI,YAAY,CAAC,CAAC;AACrD,CAAC;AAED,SAAgB,SAAS,CAAC,SAAiB,EAAE,IAAiB,EAAE,KAAa;IACzE,MAAM,UAAU,GAAG,IAAI,GAAG,EAAuB,CAAC;IAClD,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACrB,IAAI,IAAI,GAAG,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,CAAC,IAAI,EAAE,CAAC;YACR,MAAM,IAAI,GAAG,WAAW,CAAC,SAAS,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC;YAC9C,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;gBACvB,MAAM,IAAI,gBAAS,CAAC,GAAG,KAAK,YAAY,KAAK,CAAC,GAAG,CAAC,UAAU,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;YAClG,CAAC;YACD,MAAM,GAAG,GAAG,IAAA,YAAK,EAAC,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;YACvD,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;gBACZ,MAAM,IAAI,gBAAS,CAAC,GAAG,KAAK,KAAK,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,wDAAwD,CAAC,CAAC;YAClH,CAAC;YACD,IAAI,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,MAAM,CAAC,CAAC,CAAC;YAC/D,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QACnC,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YACrB,MAAM,IAAI,gBAAS,CAAC,GAAG,KAAK,YAAY,KAAK,CAAC,GAAG,CAAC,oBAAoB,GAAG,CAAC,IAAI,iCAAiC,GAAG,CAAC,IAAI,aAAa,CAAC,CAAC;QAC1I,CAAC;IACL,CAAC;AACL,CAAC;AAED,mEAAmE;AACnE,6DAA6D;AAC7D,MAAa,WAAW;IAGS;IAFZ,KAAK,GAAG,IAAI,GAAG,EAAsC,CAAC;IAEvE,YAA6B,SAAiB;QAAjB,cAAS,GAAT,SAAS,CAAQ;IAAG,CAAC;IAE1C,OAAO,CAAC,IAAY;QACxB,IAAI,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAClC,IAAI,CAAC,MAAM,EAAE,CAAC;YACV,MAAM,MAAM,GAAG,IAAA,iCAAY,EAAC,MAAM,EAAE,CAAC,SAAS,EAAE,eAAe,EAAE,MAAM,EAAE,WAAW,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC,EAAE;gBACzG,QAAQ,EAAE,MAAM;gBAChB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC;aACpC,CAAC,CAAC;YACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAA+B,CAAC;YAC1D,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACjC,CAAC;QACD,OAAO,MAAM,CAAC;IAClB,CAAC;IAED,oDAAoD;IACpD,OAAO,CAAC,IAAiB;QACrB,MAAM,QAAQ,GAA6B,EAAE,CAAC;QAC9C,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACrB,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAC9C,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;gBAC5B,MAAM,IAAI,gBAAS,CAAC,UAAU,KAAK,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;YACjE,CAAC;YACD,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC;QACjC,CAAC;QACD,OAAO,QAAQ,CAAC;IACpB,CAAC;CACJ;AA9BD,kCA8BC"}
@@ -0,0 +1,27 @@
1
+ export declare const API_GROUP = "platform.pattform.io";
2
+ export declare const ANNOTATION_CAPABILITY_VERSION = "platform.pattform.io/capability-version";
3
+ export declare const ANNOTATION_STACK = "platform.pattform.io/stack";
4
+ export interface SpecDocument {
5
+ apiVersion: string;
6
+ kind: string;
7
+ metadata: {
8
+ name: string;
9
+ namespace?: string;
10
+ annotations?: {
11
+ [key: string]: string;
12
+ };
13
+ };
14
+ spec: unknown;
15
+ file: string;
16
+ }
17
+ export declare class SpecError extends Error {
18
+ }
19
+ export declare function loadSpecs(deployDir: string): SpecDocument[];
20
+ export declare function specId(spec: SpecDocument): string;
21
+ export declare function capabilityVersion(spec: SpecDocument): string;
22
+ export declare function kebab(kind: string): string;
23
+ export declare function capabilityPackage(kind: string): string;
24
+ export declare function stackName(spec: SpecDocument, scope: "Namespaced" | "Cluster"): {
25
+ project: string;
26
+ stack: string;
27
+ };
@@ -0,0 +1,154 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.SpecError = exports.ANNOTATION_STACK = exports.ANNOTATION_CAPABILITY_VERSION = exports.API_GROUP = void 0;
37
+ exports.loadSpecs = loadSpecs;
38
+ exports.specId = specId;
39
+ exports.capabilityVersion = capabilityVersion;
40
+ exports.kebab = kebab;
41
+ exports.capabilityPackage = capabilityPackage;
42
+ exports.stackName = stackName;
43
+ const fs = __importStar(require("node:fs"));
44
+ const path = __importStar(require("node:path"));
45
+ const yaml_1 = require("yaml");
46
+ // Loading and checking the Kubernetes-style envelope of every spec in an app
47
+ // repo's deploy/ folder. Capability-specific validation (the schema) happens
48
+ // later, once the pinned capability is installed.
49
+ exports.API_GROUP = "platform.pattform.io";
50
+ exports.ANNOTATION_CAPABILITY_VERSION = `${exports.API_GROUP}/capability-version`;
51
+ exports.ANNOTATION_STACK = `${exports.API_GROUP}/stack`;
52
+ const DNS_1123_LABEL = /^[a-z0-9]([-a-z0-9]*[a-z0-9])?$/;
53
+ const SEMVER = /^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/;
54
+ class SpecError extends Error {
55
+ }
56
+ exports.SpecError = SpecError;
57
+ function isSecretsFile(file) {
58
+ return file.endsWith(".sops.yaml") || file.endsWith(".sops.yml");
59
+ }
60
+ // Every YAML document in deploy/ except SOPS secret files.
61
+ function loadSpecs(deployDir) {
62
+ if (!fs.existsSync(deployDir)) {
63
+ throw new SpecError(`${deployDir} does not exist`);
64
+ }
65
+ const files = fs.readdirSync(deployDir)
66
+ .filter(file => /\.ya?ml$/.test(file) && !isSecretsFile(file))
67
+ .sort();
68
+ const specs = [];
69
+ for (const file of files) {
70
+ const docs = (0, yaml_1.parseAllDocuments)(fs.readFileSync(path.join(deployDir, file), "utf8"));
71
+ for (const doc of docs) {
72
+ if (doc.errors.length) {
73
+ throw new SpecError(`${file}: ${doc.errors[0].message}`);
74
+ }
75
+ const value = doc.toJSON();
76
+ if (value === null || value === undefined)
77
+ continue; // empty document
78
+ specs.push(checkEnvelope(value, file));
79
+ }
80
+ }
81
+ const seen = new Set();
82
+ for (const spec of specs) {
83
+ const id = specId(spec);
84
+ if (seen.has(id)) {
85
+ throw new SpecError(`${spec.file}: ${id} is declared more than once`);
86
+ }
87
+ seen.add(id);
88
+ }
89
+ return specs;
90
+ }
91
+ function checkEnvelope(value, file) {
92
+ const where = (field) => `${file}: ${field}`;
93
+ if (typeof value !== "object" || Array.isArray(value)) {
94
+ throw new SpecError(`${file}: each document must be a mapping`);
95
+ }
96
+ const { apiVersion, kind, metadata, spec } = value;
97
+ if (typeof apiVersion !== "string" || !apiVersion.startsWith(`${exports.API_GROUP}/`)) {
98
+ throw new SpecError(where(`apiVersion must be ${exports.API_GROUP}/<version>, got ${JSON.stringify(apiVersion)}`));
99
+ }
100
+ if (typeof kind !== "string" || !/^[A-Z][A-Za-z0-9]*$/.test(kind)) {
101
+ throw new SpecError(where(`kind must be PascalCase, got ${JSON.stringify(kind)}`));
102
+ }
103
+ if (!metadata || typeof metadata.name !== "string" || !DNS_1123_LABEL.test(metadata.name) || metadata.name.length > 63) {
104
+ throw new SpecError(where(`metadata.name must be a DNS-1123 label (lowercase letters, digits, hyphens, max 63)`));
105
+ }
106
+ if (metadata.namespace !== undefined && (typeof metadata.namespace !== "string" || !DNS_1123_LABEL.test(metadata.namespace))) {
107
+ throw new SpecError(where(`metadata.namespace must be a DNS-1123 label`));
108
+ }
109
+ const version = metadata.annotations?.[exports.ANNOTATION_CAPABILITY_VERSION];
110
+ if (typeof version !== "string" || !SEMVER.test(version)) {
111
+ throw new SpecError(where(`${kind}/${metadata.name} needs annotation ${exports.ANNOTATION_CAPABILITY_VERSION} set to an exact version, e.g. "1.0.0"`));
112
+ }
113
+ if ("status" in value) {
114
+ throw new SpecError(where(`status is written by the engine and must not be committed`));
115
+ }
116
+ return { apiVersion, kind, metadata, spec: spec ?? {}, file };
117
+ }
118
+ function specId(spec) {
119
+ return spec.metadata.namespace
120
+ ? `${spec.kind}/${spec.metadata.namespace}/${spec.metadata.name}`
121
+ : `${spec.kind}/${spec.metadata.name}`;
122
+ }
123
+ function capabilityVersion(spec) {
124
+ return spec.metadata.annotations[exports.ANNOTATION_CAPABILITY_VERSION];
125
+ }
126
+ function kebab(kind) {
127
+ return kind.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
128
+ }
129
+ function capabilityPackage(kind) {
130
+ return `@pattform/capability-${kebab(kind)}`;
131
+ }
132
+ // organization/<kind>/<namespace>.<name>, organization/client/<name> for
133
+ // cluster-scoped kinds, or the stack named by the adoption annotation.
134
+ function stackName(spec, scope) {
135
+ const adopt = spec.metadata.annotations?.[exports.ANNOTATION_STACK];
136
+ if (adopt) {
137
+ const match = /^([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+)$/.exec(adopt);
138
+ if (!match) {
139
+ throw new SpecError(`${spec.file}: ${exports.ANNOTATION_STACK} must be <project>/<stack>, got ${adopt}`);
140
+ }
141
+ return { project: match[1], stack: match[2] };
142
+ }
143
+ if (scope === "Namespaced") {
144
+ if (!spec.metadata.namespace) {
145
+ throw new SpecError(`${spec.file}: ${spec.kind} is namespaced, so metadata.namespace (the client) is required`);
146
+ }
147
+ return { project: kebab(spec.kind), stack: `${spec.metadata.namespace}.${spec.metadata.name}` };
148
+ }
149
+ if (spec.metadata.namespace) {
150
+ throw new SpecError(`${spec.file}: ${spec.kind} is cluster-scoped, so it takes no metadata.namespace`);
151
+ }
152
+ return { project: kebab(spec.kind), stack: spec.metadata.name };
153
+ }
154
+ //# sourceMappingURL=spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spec.js","sourceRoot":"","sources":["../../src/spec.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkCA,8BA8BC;AA8BD,wBAIC;AAED,8CAEC;AAED,sBAEC;AAED,8CAEC;AAID,8BAmBC;AArID,4CAA8B;AAC9B,gDAAkC;AAClC,+BAAyC;AAEzC,6EAA6E;AAC7E,6EAA6E;AAC7E,kDAAkD;AAErC,QAAA,SAAS,GAAG,sBAAsB,CAAC;AACnC,QAAA,6BAA6B,GAAG,GAAG,iBAAS,qBAAqB,CAAC;AAClE,QAAA,gBAAgB,GAAG,GAAG,iBAAS,QAAQ,CAAC;AAcrD,MAAM,cAAc,GAAG,iCAAiC,CAAC;AACzD,MAAM,MAAM,GAAG,mCAAmC,CAAC;AAEnD,MAAa,SAAU,SAAQ,KAAK;CAAG;AAAvC,8BAAuC;AAEvC,SAAS,aAAa,CAAC,IAAY;IAC/B,OAAO,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;AACrE,CAAC;AAED,2DAA2D;AAC3D,SAAgB,SAAS,CAAC,SAAiB;IACvC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,SAAS,CAAC,GAAG,SAAS,iBAAiB,CAAC,CAAC;IACvD,CAAC;IACD,MAAM,KAAK,GAAG,EAAE,CAAC,WAAW,CAAC,SAAS,CAAC;SAClC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;SAC7D,IAAI,EAAE,CAAC;IAEZ,MAAM,KAAK,GAAmB,EAAE,CAAC;IACjC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACvB,MAAM,IAAI,GAAG,IAAA,wBAAiB,EAAC,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;QACpF,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACrB,IAAI,GAAG,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;gBACpB,MAAM,IAAI,SAAS,CAAC,GAAG,IAAI,KAAK,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;YAC7D,CAAC;YACD,MAAM,KAAK,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC;YAC3B,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS;gBAAE,SAAS,CAAC,iBAAiB;YACtE,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC;QAC3C,CAAC;IACL,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACvB,MAAM,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;QACxB,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC;YACf,MAAM,IAAI,SAAS,CAAC,GAAG,IAAI,CAAC,IAAI,KAAK,EAAE,6BAA6B,CAAC,CAAC;QAC1E,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACjB,CAAC;IACD,OAAO,KAAK,CAAC;AACjB,CAAC;AAED,SAAS,aAAa,CAAC,KAAU,EAAE,IAAY;IAC3C,MAAM,KAAK,GAAG,CAAC,KAAa,EAAE,EAAE,CAAC,GAAG,IAAI,KAAK,KAAK,EAAE,CAAC;IACrD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACpD,MAAM,IAAI,SAAS,CAAC,GAAG,IAAI,mCAAmC,CAAC,CAAC;IACpE,CAAC;IACD,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,KAAK,CAAC;IACnD,IAAI,OAAO,UAAU,KAAK,QAAQ,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,GAAG,iBAAS,GAAG,CAAC,EAAE,CAAC;QAC5E,MAAM,IAAI,SAAS,CAAC,KAAK,CAAC,sBAAsB,iBAAS,mBAAmB,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC;IAC/G,CAAC;IACD,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAChE,MAAM,IAAI,SAAS,CAAC,KAAK,CAAC,gCAAgC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;IACvF,CAAC;IACD,IAAI,CAAC,QAAQ,IAAI,OAAO,QAAQ,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,QAAQ,CAAC,IAAI,CAAC,MAAM,GAAG,EAAE,EAAE,CAAC;QACrH,MAAM,IAAI,SAAS,CAAC,KAAK,CAAC,qFAAqF,CAAC,CAAC,CAAC;IACtH,CAAC;IACD,IAAI,QAAQ,CAAC,SAAS,KAAK,SAAS,IAAI,CAAC,OAAO,QAAQ,CAAC,SAAS,KAAK,QAAQ,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC;QAC3H,MAAM,IAAI,SAAS,CAAC,KAAK,CAAC,6CAA6C,CAAC,CAAC,CAAC;IAC9E,CAAC;IACD,MAAM,OAAO,GAAG,QAAQ,CAAC,WAAW,EAAE,CAAC,qCAA6B,CAAC,CAAC;IACtE,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QACvD,MAAM,IAAI,SAAS,CAAC,KAAK,CAAC,GAAG,IAAI,IAAI,QAAQ,CAAC,IAAI,qBAAqB,qCAA6B,wCAAwC,CAAC,CAAC,CAAC;IACnJ,CAAC;IACD,IAAI,QAAQ,IAAI,KAAK,EAAE,CAAC;QACpB,MAAM,IAAI,SAAS,CAAC,KAAK,CAAC,2DAA2D,CAAC,CAAC,CAAC;IAC5F,CAAC;IACD,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,IAAI,EAAE,EAAE,IAAI,EAAE,CAAC;AAClE,CAAC;AAED,SAAgB,MAAM,CAAC,IAAkB;IACrC,OAAO,IAAI,CAAC,QAAQ,CAAC,SAAS;QAC1B,CAAC,CAAC,GAAG,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,QAAQ,CAAC,SAAS,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE;QACjE,CAAC,CAAC,GAAG,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;AAC/C,CAAC;AAED,SAAgB,iBAAiB,CAAC,IAAkB;IAChD,OAAO,IAAI,CAAC,QAAQ,CAAC,WAAY,CAAC,qCAA6B,CAAC,CAAC;AACrE,CAAC;AAED,SAAgB,KAAK,CAAC,IAAY;IAC9B,OAAO,IAAI,CAAC,OAAO,CAAC,oBAAoB,EAAE,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC;AACrE,CAAC;AAED,SAAgB,iBAAiB,CAAC,IAAY;IAC1C,OAAO,wBAAwB,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;AACjD,CAAC;AAED,yEAAyE;AACzE,uEAAuE;AACvE,SAAgB,SAAS,CAAC,IAAkB,EAAE,KAA+B;IACzE,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC,wBAAgB,CAAC,CAAC;IAC5D,IAAI,KAAK,EAAE,CAAC;QACR,MAAM,KAAK,GAAG,wCAAwC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACnE,IAAI,CAAC,KAAK,EAAE,CAAC;YACT,MAAM,IAAI,SAAS,CAAC,GAAG,IAAI,CAAC,IAAI,KAAK,wBAAgB,mCAAmC,KAAK,EAAE,CAAC,CAAC;QACrG,CAAC;QACD,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;IAClD,CAAC;IACD,IAAI,KAAK,KAAK,YAAY,EAAE,CAAC;QACzB,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,EAAE,CAAC;YAC3B,MAAM,IAAI,SAAS,CAAC,GAAG,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,gEAAgE,CAAC,CAAC;QACpH,CAAC;QACD,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC,SAAS,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC;IACpG,CAAC;IACD,IAAI,IAAI,CAAC,QAAQ,CAAC,SAAS,EAAE,CAAC;QAC1B,MAAM,IAAI,SAAS,CAAC,GAAG,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,uDAAuD,CAAC,CAAC;IAC3G,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;AACpE,CAAC"}
@@ -0,0 +1,20 @@
1
+ export declare const BACKEND_URL = "s3://pattform-pulumi-state?region=ap-southeast-2";
2
+ export declare const SECRETS_PROVIDER = "awskms://alias/pattform-pulumi?region=ap-southeast-2";
3
+ export interface WorkspaceOptions {
4
+ root: string;
5
+ capabilitiesDir?: string;
6
+ }
7
+ export declare function prepareWorkspace(options: WorkspaceOptions, dirName: string, kind: string, version: string): string;
8
+ export declare function writeProject(workDir: string, project: string, kind: string, version: string): void;
9
+ export interface LoadedCapability {
10
+ kind: string;
11
+ scope: "Namespaced" | "Cluster";
12
+ schemas: {
13
+ [apiVersion: string]: object;
14
+ };
15
+ secretRefs?(spec: unknown): {
16
+ name: string;
17
+ key: string;
18
+ }[];
19
+ }
20
+ export declare function loadCapability(workDir: string, kind: string): LoadedCapability;
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.SECRETS_PROVIDER = exports.BACKEND_URL = void 0;
37
+ exports.prepareWorkspace = prepareWorkspace;
38
+ exports.writeProject = writeProject;
39
+ exports.loadCapability = loadCapability;
40
+ const fs = __importStar(require("node:fs"));
41
+ const path = __importStar(require("node:path"));
42
+ const node_child_process_1 = require("node:child_process");
43
+ const spec_1 = require("./spec");
44
+ // One throwaway Pulumi project per stack under .pattform/work/, holding a
45
+ // generated Pulumi.yaml, a one-line program and the pinned capability
46
+ // installed with npm. The capability's own dependencies fix the provider
47
+ // versions, so a capability version fully determines what the stack runs.
48
+ exports.BACKEND_URL = "s3://pattform-pulumi-state?region=ap-southeast-2";
49
+ exports.SECRETS_PROVIDER = "awskms://alias/pattform-pulumi?region=ap-southeast-2";
50
+ function npm(args, cwd) {
51
+ return (0, node_child_process_1.execFileSync)("npm", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
52
+ }
53
+ function packLocal(capabilitiesDir, workspace, into) {
54
+ const dir = path.join(capabilitiesDir, "packages", workspace);
55
+ if (!fs.existsSync(path.join(dir, "dist"))) {
56
+ throw new Error(`${dir} is not built; run npm run build in ${capabilitiesDir}`);
57
+ }
58
+ const tarball = npm(["pack", "--pack-destination", into, "--silent"], dir).trim().split("\n").pop();
59
+ return `file:./.tarballs/${tarball}`;
60
+ }
61
+ // Installs the pinned capability into .pattform/work/<dirName>/. The Pulumi
62
+ // project file is written separately (writeProject) once the capability's
63
+ // scope, and so the stack name, is known.
64
+ function prepareWorkspace(options, dirName, kind, version) {
65
+ const workDir = path.join(options.root, ".pattform", "work", dirName);
66
+ fs.mkdirSync(workDir, { recursive: true });
67
+ const pkg = (0, spec_1.capabilityPackage)(kind);
68
+ const dependencies = {};
69
+ const overrides = {};
70
+ if (options.capabilitiesDir) {
71
+ const tarballs = path.join(workDir, ".tarballs");
72
+ fs.rmSync(tarballs, { recursive: true, force: true });
73
+ fs.mkdirSync(tarballs);
74
+ const sdk = packLocal(options.capabilitiesDir, "sdk", tarballs);
75
+ dependencies[pkg] = packLocal(options.capabilitiesDir, (0, spec_1.kebab)(kind), tarballs);
76
+ dependencies["@pattform/capability-sdk"] = sdk;
77
+ overrides["@pattform/capability-sdk"] = sdk;
78
+ }
79
+ else {
80
+ dependencies[pkg] = version;
81
+ }
82
+ const manifest = { name: `pattform-stack-${dirName}`.toLowerCase().replace(/[^a-z0-9-]/g, "-"), private: true, dependencies, overrides };
83
+ const manifestText = JSON.stringify(manifest, null, 2);
84
+ const installedMarker = path.join(workDir, ".installed.json");
85
+ const upToDate = !options.capabilitiesDir
86
+ && fs.existsSync(installedMarker)
87
+ && fs.readFileSync(installedMarker, "utf8") === manifestText;
88
+ fs.writeFileSync(path.join(workDir, "package.json"), manifestText);
89
+ fs.writeFileSync(path.join(workDir, "index.js"), `module.exports = require("@pattform/capability-sdk").run(require("${pkg}").default);\n`);
90
+ if (!upToDate) {
91
+ npm(["install", "--no-audit", "--no-fund", "--omit=dev", "--loglevel=error"], workDir);
92
+ fs.writeFileSync(installedMarker, manifestText);
93
+ }
94
+ return workDir;
95
+ }
96
+ function writeProject(workDir, project, kind, version) {
97
+ fs.writeFileSync(path.join(workDir, "Pulumi.yaml"), [
98
+ `name: ${project}`,
99
+ `runtime: nodejs`,
100
+ `main: index.js`,
101
+ `description: Generated by pattform-engine for ${kind} (${(0, spec_1.capabilityPackage)(kind)}@${version}). Do not edit.`,
102
+ `backend:`,
103
+ ` url: ${exports.BACKEND_URL}`,
104
+ ``,
105
+ ].join("\n"));
106
+ }
107
+ function loadCapability(workDir, kind) {
108
+ const modulePath = path.join(workDir, "node_modules", (0, spec_1.capabilityPackage)(kind));
109
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
110
+ const capability = require(modulePath).default;
111
+ if (!capability || capability.kind !== kind) {
112
+ throw new Error(`${(0, spec_1.capabilityPackage)(kind)} does not export the ${kind} capability`);
113
+ }
114
+ return capability;
115
+ }
116
+ //# sourceMappingURL=workspace.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace.js","sourceRoot":"","sources":["../../src/workspace.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,26 @@
1
1
  {
2
2
  "name": "@pattform/engine",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Turns an app repo's deploy/*.yaml specs into Pulumi stacks using Pattform capabilities (ADR-001)",
5
+ "license": "SEE LICENSE IN LICENSE",
6
+ "bin": { "pattform": "dist/src/cli.js" },
7
+ "main": "dist/src/index.js",
8
+ "types": "dist/src/index.d.ts",
9
+ "files": ["dist/src"],
10
+ "repository": { "type": "git", "url": "git+https://github.com/Pattform/pattform-engine.git" },
11
+ "publishConfig": { "access": "public" },
12
+ "scripts": {
13
+ "build": "tsc -p .",
14
+ "test": "npm run build && node --test 'dist/test/*.test.js'"
15
+ },
16
+ "dependencies": {
17
+ "@pulumi/pulumi": "^3.253.0",
18
+ "ajv": "^8.17.1",
19
+ "ajv-formats": "^3.0.1",
20
+ "yaml": "^2.6.0"
21
+ },
22
+ "devDependencies": {
23
+ "@types/node": "^22.10.0",
24
+ "typescript": "^5.6.3"
25
+ }
26
+ }