@figulus/schema 0.4.0-alpha-dev → 0.5.0-alpha-dev-1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.openapi-meta/index.ts +12 -0
- package/dist/core/entities/figspec.d.ts +659 -0
- package/dist/core/entities/figspec.d.ts.map +1 -0
- package/dist/core/entities/figspec.js +73 -0
- package/dist/core/entities/figspec.js.map +1 -0
- package/dist/core/entities/figstack.d.ts +139 -0
- package/dist/core/entities/figstack.d.ts.map +1 -0
- package/dist/core/entities/figstack.js +79 -0
- package/dist/core/entities/figstack.js.map +1 -0
- package/dist/core/entities/index.d.ts +3 -0
- package/dist/core/entities/index.d.ts.map +1 -0
- package/dist/core/entities/index.js +3 -0
- package/dist/core/entities/index.js.map +1 -0
- package/dist/core/execute.d.ts +64 -0
- package/dist/core/execute.d.ts.map +1 -0
- package/dist/core/execute.js +45 -0
- package/dist/core/execute.js.map +1 -0
- package/dist/core/generic.d.ts +35 -0
- package/dist/core/generic.d.ts.map +1 -0
- package/dist/core/generic.js +28 -0
- package/dist/core/generic.js.map +1 -0
- package/dist/core/index.d.ts +6 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +6 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/primitives.d.ts +3 -0
- package/dist/core/primitives.d.ts.map +1 -0
- package/dist/core/primitives.js +11 -0
- package/dist/core/primitives.js.map +1 -0
- package/dist/core/volumes.d.ts +160 -0
- package/dist/core/volumes.d.ts.map +1 -0
- package/dist/core/volumes.js +100 -0
- package/dist/core/volumes.js.map +1 -0
- package/dist/engine/deprecated/index.d.ts +2 -0
- package/dist/engine/deprecated/index.d.ts.map +1 -0
- package/dist/engine/deprecated/index.js +2 -0
- package/dist/engine/deprecated/index.js.map +1 -0
- package/dist/engine/deprecated/run-request.d.ts +73 -0
- package/dist/engine/deprecated/run-request.d.ts.map +1 -0
- package/dist/engine/deprecated/run-request.js +47 -0
- package/dist/engine/deprecated/run-request.js.map +1 -0
- package/dist/engine/dry-run.d.ts +15 -0
- package/dist/engine/dry-run.d.ts.map +1 -0
- package/dist/engine/dry-run.js +12 -0
- package/dist/engine/dry-run.js.map +1 -0
- package/dist/engine/health.d.ts +11 -0
- package/dist/engine/health.d.ts.map +1 -0
- package/dist/engine/health.js +9 -0
- package/dist/engine/health.js.map +1 -0
- package/dist/engine/images.d.ts +49 -0
- package/dist/engine/images.d.ts.map +1 -0
- package/dist/engine/images.js +44 -0
- package/dist/engine/images.js.map +1 -0
- package/dist/engine/index.d.ts +9 -0
- package/dist/engine/index.d.ts.map +1 -0
- package/dist/engine/index.js +9 -0
- package/dist/engine/index.js.map +1 -0
- package/dist/engine/paths/containers.d.ts +8 -0
- package/dist/engine/paths/containers.d.ts.map +1 -0
- package/dist/engine/paths/containers.js +297 -0
- package/dist/engine/paths/containers.js.map +1 -0
- package/dist/engine/paths/dry-run.d.ts +3 -0
- package/dist/engine/paths/dry-run.d.ts.map +1 -0
- package/dist/engine/paths/dry-run.js +43 -0
- package/dist/engine/paths/dry-run.js.map +1 -0
- package/dist/engine/paths/health.d.ts +3 -0
- package/dist/engine/paths/health.d.ts.map +1 -0
- package/dist/engine/paths/health.js +36 -0
- package/dist/engine/paths/health.js.map +1 -0
- package/dist/engine/paths/images.d.ts +5 -0
- package/dist/engine/paths/images.d.ts.map +1 -0
- package/dist/engine/paths/images.js +144 -0
- package/dist/engine/paths/images.js.map +1 -0
- package/dist/engine/paths/index.d.ts +8 -0
- package/dist/engine/paths/index.d.ts.map +1 -0
- package/dist/engine/paths/index.js +8 -0
- package/dist/engine/paths/index.js.map +1 -0
- package/dist/engine/paths/sessions.d.ts +10 -0
- package/dist/engine/paths/sessions.d.ts.map +1 -0
- package/dist/engine/paths/sessions.js +484 -0
- package/dist/engine/paths/sessions.js.map +1 -0
- package/dist/engine/paths/stacks.d.ts +6 -0
- package/dist/engine/paths/stacks.d.ts.map +1 -0
- package/dist/engine/paths/stacks.js +175 -0
- package/dist/engine/paths/stacks.js.map +1 -0
- package/dist/engine/paths/volumes.d.ts +6 -0
- package/dist/engine/paths/volumes.d.ts.map +1 -0
- package/dist/engine/paths/volumes.js +164 -0
- package/dist/engine/paths/volumes.js.map +1 -0
- package/dist/engine/response.d.ts +8 -0
- package/dist/engine/response.d.ts.map +1 -0
- package/dist/engine/response.js +9 -0
- package/dist/engine/response.js.map +1 -0
- package/dist/engine/sessions.d.ts +343 -0
- package/dist/engine/sessions.d.ts.map +1 -0
- package/dist/engine/sessions.js +118 -0
- package/dist/engine/sessions.js.map +1 -0
- package/dist/engine/volumes.d.ts +52 -0
- package/dist/engine/volumes.d.ts.map +1 -0
- package/dist/engine/volumes.js +39 -0
- package/dist/engine/volumes.js.map +1 -0
- package/dist/index.d.ts +2 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -8
- package/dist/index.js.map +1 -1
- package/dist/registry/index.d.ts +3 -0
- package/dist/registry/index.d.ts.map +1 -0
- package/dist/registry/index.js +3 -0
- package/dist/registry/index.js.map +1 -0
- package/dist/registry/json-schema.d.ts +82 -0
- package/dist/registry/json-schema.d.ts.map +1 -0
- package/dist/registry/json-schema.js +149 -0
- package/dist/registry/json-schema.js.map +1 -0
- package/dist/registry/metadata.d.ts +216 -0
- package/dist/registry/metadata.d.ts.map +1 -0
- package/dist/registry/metadata.js +143 -0
- package/dist/registry/metadata.js.map +1 -0
- package/package.json +10 -3
- package/scripts/fix-openapi-3.1.ts +183 -0
- package/scripts/generate-openapi-data.ts +51 -0
- package/scripts/generate-openapi-meta.ts +540 -0
- package/src/core/entities/figspec.ts +94 -0
- package/src/core/entities/figstack.ts +101 -0
- package/src/core/entities/index.ts +2 -0
- package/src/core/execute.ts +66 -0
- package/src/core/generic.ts +38 -0
- package/src/core/index.ts +5 -0
- package/src/core/primitives.ts +12 -0
- package/src/core/volumes.ts +124 -0
- package/src/engine/deprecated/index.ts +1 -0
- package/src/engine/deprecated/run-request.ts +53 -0
- package/src/engine/dry-run.ts +14 -0
- package/src/engine/health.ts +11 -0
- package/src/engine/images.ts +58 -0
- package/src/engine/index.ts +8 -0
- package/src/engine/paths/containers.ts +304 -0
- package/src/engine/paths/dry-run.ts +44 -0
- package/src/engine/paths/health.ts +37 -0
- package/src/engine/paths/images.ts +148 -0
- package/src/engine/paths/index.ts +7 -0
- package/src/engine/paths/sessions.ts +493 -0
- package/src/engine/paths/stacks.ts +179 -0
- package/src/engine/paths/volumes.ts +168 -0
- package/src/engine/response.ts +11 -0
- package/src/engine/sessions.ts +146 -0
- package/src/engine/volumes.ts +53 -0
- package/src/index.ts +2 -8
- package/src/registry/index.ts +2 -0
- package/src/registry/json-schema.ts +178 -0
- package/src/registry/metadata.ts +181 -0
- package/tests/figspec.test.ts +1 -1
- package/tests/schemas.test.ts +31 -31
- package/dist/figspec.d.ts +0 -4394
- package/dist/figspec.d.ts.map +0 -1
- package/dist/figspec.js +0 -214
- package/dist/figspec.js.map +0 -1
- package/dist/figstack.d.ts +0 -419
- package/dist/figstack.d.ts.map +0 -1
- package/dist/figstack.js +0 -72
- package/dist/figstack.js.map +0 -1
- package/dist/health.d.ts +0 -16
- package/dist/health.d.ts.map +0 -1
- package/dist/health.js +0 -7
- package/dist/health.js.map +0 -1
- package/dist/image.d.ts +0 -115
- package/dist/image.d.ts.map +0 -1
- package/dist/image.js +0 -42
- package/dist/image.js.map +0 -1
- package/dist/run-request.d.ts +0 -244
- package/dist/run-request.d.ts.map +0 -1
- package/dist/run-request.js +0 -43
- package/dist/run-request.js.map +0 -1
- package/dist/session.d.ts +0 -447
- package/dist/session.d.ts.map +0 -1
- package/dist/session.js +0 -78
- package/dist/session.js.map +0 -1
- package/dist/shared.d.ts +0 -90
- package/dist/shared.d.ts.map +0 -1
- package/dist/shared.js +0 -41
- package/dist/shared.js.map +0 -1
- package/dist/volume.d.ts +0 -140
- package/dist/volume.d.ts.map +0 -1
- package/dist/volume.js +0 -37
- package/dist/volume.js.map +0 -1
- package/src/figspec.ts +0 -279
- package/src/figstack.ts +0 -92
- package/src/health.ts +0 -8
- package/src/image.ts +0 -55
- package/src/run-request.ts +0 -55
- package/src/session.ts +0 -101
- package/src/shared.ts +0 -56
- package/src/volume.ts +0 -50
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { parse, stringify } from "yaml";
|
|
2
|
+
import { readFileSync, writeFileSync } from "fs";
|
|
3
|
+
|
|
4
|
+
const inputPath = process.argv[2];
|
|
5
|
+
const outputPath = process.argv[3] ?? inputPath;
|
|
6
|
+
|
|
7
|
+
if (!inputPath) {
|
|
8
|
+
console.error("Usage: npx tsx fix-nullable.ts <input.yaml> [output.yaml]");
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Recursively walks a parsed YAML/JSON object and fixes OpenAPI 3.1 schema issues
|
|
14
|
+
* produced by @asteasolutions/zod-to-openapi when generating from Zod schemas.
|
|
15
|
+
*
|
|
16
|
+
* Fixes:
|
|
17
|
+
*
|
|
18
|
+
* 1. Standalone nullable member in anyOf/oneOf/allOf:
|
|
19
|
+
* { anyOf: [..., { nullable: true }] }
|
|
20
|
+
* → { anyOf: [..., { type: "null" }] }
|
|
21
|
+
*
|
|
22
|
+
* 2. Sibling nullable on a schema object:
|
|
23
|
+
* { type: "string", nullable: true }
|
|
24
|
+
* → { anyOf: [{ type: "string" }, { type: "null" }] }
|
|
25
|
+
*
|
|
26
|
+
* 3. OpenAPI 3.0-style exclusiveMinimum as boolean:
|
|
27
|
+
* { minimum: 0, exclusiveMinimum: true }
|
|
28
|
+
* → { exclusiveMinimum: 0 }
|
|
29
|
+
*
|
|
30
|
+
* 4. Zod unions generated as anyOf when they should be oneOf:
|
|
31
|
+
* { anyOf: [{ $ref: ... }, { $ref: ... }, { type: "string" }] }
|
|
32
|
+
* → { oneOf: [{ $ref: ... }, { $ref: ... }, { type: "string" }] }
|
|
33
|
+
* (when items are mutually exclusive: all have different types, or all are refs, etc)
|
|
34
|
+
*
|
|
35
|
+
* All are produced by @asteasolutions/zod-to-openapi when handling z.null(),
|
|
36
|
+
* z.nullable(), z.union(), or numeric constraints in Zod schemas.
|
|
37
|
+
*/
|
|
38
|
+
function fixNullable(node: unknown): unknown {
|
|
39
|
+
if (Array.isArray(node)) {
|
|
40
|
+
const fixed = node.map(fixNullable);
|
|
41
|
+
|
|
42
|
+
// Pattern 1: replace { nullable: true } entries in anyOf/oneOf/allOf arrays
|
|
43
|
+
// with { type: "null" }
|
|
44
|
+
return fixed.map((item) => {
|
|
45
|
+
if (
|
|
46
|
+
item !== null &&
|
|
47
|
+
typeof item === "object" &&
|
|
48
|
+
!Array.isArray(item) &&
|
|
49
|
+
(item as Record<string, unknown>)["nullable"] === true &&
|
|
50
|
+
Object.keys(item as object).length === 1
|
|
51
|
+
) {
|
|
52
|
+
return { type: "null" };
|
|
53
|
+
}
|
|
54
|
+
return item;
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (node !== null && typeof node === "object") {
|
|
59
|
+
const obj = node as Record<string, unknown>;
|
|
60
|
+
|
|
61
|
+
// Recurse into all child values first
|
|
62
|
+
const result: Record<string, unknown> = {};
|
|
63
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
64
|
+
result[key] = fixNullable(value);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Pattern 2: sibling nullable: true on a schema with a type field
|
|
68
|
+
// { type: "string", nullable: true, ...rest }
|
|
69
|
+
// → { anyOf: [{ type: "string", ...rest }, { type: "null" }] }
|
|
70
|
+
if (result["nullable"] === true && result["type"] !== undefined) {
|
|
71
|
+
const { nullable, ...rest } = result;
|
|
72
|
+
void nullable; // consumed
|
|
73
|
+
return {
|
|
74
|
+
anyOf: [rest, { type: "null" }],
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Pattern 3: standalone nullable: true with no type (already handled in
|
|
79
|
+
// array case above, but catch any that appear as object values directly)
|
|
80
|
+
if (result["nullable"] === true && result["type"] === undefined) {
|
|
81
|
+
return { type: "null" };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Pattern 4: OpenAPI 3.0-style exclusiveMinimum as boolean (should be numeric in 3.1)
|
|
85
|
+
// { minimum: 0, exclusiveMinimum: true } → { exclusiveMinimum: 0 }
|
|
86
|
+
if (result["exclusiveMinimum"] === true && result["minimum"] !== undefined) {
|
|
87
|
+
const { minimum, exclusiveMinimum, ...rest } = result;
|
|
88
|
+
void exclusiveMinimum; // consumed
|
|
89
|
+
return {
|
|
90
|
+
...rest,
|
|
91
|
+
exclusiveMinimum: minimum,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Pattern 5: Zod unions become anyOf but should be oneOf
|
|
96
|
+
// Heuristic: if anyOf items have mutually exclusive types or are all $refs, use oneOf
|
|
97
|
+
// anyOf [{ type: string }, { type: array }, { $ref: ... }] → oneOf (discriminated union)
|
|
98
|
+
if (Array.isArray(result["anyOf"]) && (result["anyOf"] as any[]).length > 0) {
|
|
99
|
+
const items = result["anyOf"] as any[];
|
|
100
|
+
let shouldBeOneOf = false;
|
|
101
|
+
|
|
102
|
+
// Check if all items have different `type` values (mutually exclusive)
|
|
103
|
+
const types = new Set<string>();
|
|
104
|
+
const allHaveType = items.every((item) => item.type !== undefined);
|
|
105
|
+
if (allHaveType) {
|
|
106
|
+
items.forEach((item) => types.add(item.type));
|
|
107
|
+
// If all items have different types, they're mutually exclusive
|
|
108
|
+
if (types.size === items.length && items.length > 1) {
|
|
109
|
+
shouldBeOneOf = true;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Check if all items are schema references ($ref)
|
|
114
|
+
const allRefs = items.every((item) => item.$ref !== undefined);
|
|
115
|
+
if (allRefs && items.length > 1) {
|
|
116
|
+
shouldBeOneOf = true;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Check if items are refs mixed with other types (e.g., string, array)
|
|
120
|
+
const refCount = items.filter((item) => item.$ref).length;
|
|
121
|
+
const otherCount = items.filter((item) => !item.$ref && item.type).length;
|
|
122
|
+
if (refCount > 0 && otherCount > 0) {
|
|
123
|
+
shouldBeOneOf = true;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (shouldBeOneOf && !result["oneOf"]) {
|
|
127
|
+
const { anyOf, ...rest } = result;
|
|
128
|
+
return {
|
|
129
|
+
...rest,
|
|
130
|
+
oneOf: anyOf,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return result;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return node;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const raw = readFileSync(inputPath, "utf-8");
|
|
142
|
+
const parsed = parse(raw);
|
|
143
|
+
const fixed = fixNullable(parsed);
|
|
144
|
+
const output = stringify(fixed, { lineWidth: 0 });
|
|
145
|
+
|
|
146
|
+
writeFileSync(outputPath, output, "utf-8");
|
|
147
|
+
|
|
148
|
+
const inputLines = raw.split("\n").length;
|
|
149
|
+
const outputLines = output.split("\n").length;
|
|
150
|
+
|
|
151
|
+
// Count how many replacements were made by diffing nullable occurrences
|
|
152
|
+
const nullableCountBefore = (raw.match(/nullable:\s*true/g) ?? []).length;
|
|
153
|
+
const nullableCountAfter = (output.match(/nullable:\s*true/g) ?? []).length;
|
|
154
|
+
const fixed_count = nullableCountBefore - nullableCountAfter;
|
|
155
|
+
|
|
156
|
+
const exclusiveMinBefore = (raw.match(/exclusiveMinimum:\s*true/g) ?? []).length;
|
|
157
|
+
const exclusiveMinAfter = (output.match(/exclusiveMinimum:\s*true/g) ?? []).length;
|
|
158
|
+
const exclusiveMinFixed = exclusiveMinBefore - exclusiveMinAfter;
|
|
159
|
+
|
|
160
|
+
const anyOfBefore = (raw.match(/anyOf:/g) ?? []).length;
|
|
161
|
+
const anyOfAfter = (output.match(/anyOf:/g) ?? []).length;
|
|
162
|
+
const anyOfToOneOfFixed = anyOfBefore - anyOfAfter;
|
|
163
|
+
|
|
164
|
+
console.log(`Input: ${inputPath} (${inputLines} lines)`);
|
|
165
|
+
console.log(`Output: ${outputPath} (${outputLines} lines)`);
|
|
166
|
+
console.log(`Fixed: ${fixed_count} nullable: true → type: "null" replacements`);
|
|
167
|
+
console.log(`Fixed: ${exclusiveMinFixed} exclusiveMinimum: true → numeric replacements`);
|
|
168
|
+
console.log(`Fixed: ${anyOfToOneOfFixed} anyOf → oneOf conversions`);
|
|
169
|
+
if (nullableCountAfter > 0) {
|
|
170
|
+
console.warn(
|
|
171
|
+
`Warning: ${nullableCountAfter} nullable: true occurrences remain — manual review needed`
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
if (exclusiveMinAfter > 0) {
|
|
175
|
+
console.warn(
|
|
176
|
+
`Warning: ${exclusiveMinAfter} exclusiveMinimum: true occurrences remain — manual review needed`
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
if (anyOfAfter > 0) {
|
|
180
|
+
console.log(
|
|
181
|
+
`ℹ️ ${anyOfAfter} anyOf schemas remain (may be intentional for overlapping unions)`
|
|
182
|
+
);
|
|
183
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { OpenApiGeneratorV3, OpenAPIRegistry } from "@asteasolutions/zod-to-openapi";
|
|
2
|
+
import { OpenAPIObjectConfig } from "@asteasolutions/zod-to-openapi/dist/v3.0/openapi-generator.js";
|
|
3
|
+
import fs, { readFileSync } from "fs";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
import YAML from "yaml";
|
|
7
|
+
import z from "zod";
|
|
8
|
+
import { registerAll } from "../.openapi-meta/index.js";
|
|
9
|
+
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
+
const __dirname = path.dirname(__filename);
|
|
12
|
+
const inputDir = path.join(__dirname, "..", ".openapi-meta", "generated");
|
|
13
|
+
const inputFile = path.join(inputDir, "data.json");
|
|
14
|
+
|
|
15
|
+
const metadata = z.object({
|
|
16
|
+
fileName: z.string(),
|
|
17
|
+
document: z.custom<OpenAPIObjectConfig>(),
|
|
18
|
+
}).parse(
|
|
19
|
+
JSON.parse(readFileSync(inputFile).toString())
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
const outputDir = path.join(__dirname, "..", "..", "..", "api");
|
|
23
|
+
const outputFile = path.join(outputDir, `${metadata.fileName}.yaml`);
|
|
24
|
+
|
|
25
|
+
// Create output directory if it doesn't exist
|
|
26
|
+
if (!fs.existsSync(outputDir)) {
|
|
27
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
const registry = registerAll(new OpenAPIRegistry());
|
|
32
|
+
|
|
33
|
+
const generator = new OpenApiGeneratorV3(registry.definitions);
|
|
34
|
+
|
|
35
|
+
const openApiDoc = generator.generateDocument(metadata.document);
|
|
36
|
+
|
|
37
|
+
// Sort components.schemas alphabetically for stable output independent of source file organization
|
|
38
|
+
if (openApiDoc.components?.schemas) {
|
|
39
|
+
openApiDoc.components.schemas = Object.fromEntries(
|
|
40
|
+
Object.entries(openApiDoc.components.schemas).sort(([a], [b]) => a.localeCompare(b))
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const yamlContent = YAML.stringify(openApiDoc);
|
|
45
|
+
fs.writeFileSync(outputFile, yamlContent);
|
|
46
|
+
|
|
47
|
+
console.log(`✅ Generated OpenAPI spec at: ${outputFile}`);
|
|
48
|
+
} catch (error) {
|
|
49
|
+
console.error("❌ Failed to generate OpenAPI spec:", error);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|