@appstrate/validation 1.1.0 → 1.2.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/package.json +4 -2
- package/src/index.ts +15 -48
- package/src/naming.ts +7 -0
- package/src/zip.ts +2 -3
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -26,13 +26,14 @@ export const jsonSchemaObjectSchema = z.object({
|
|
|
26
26
|
propertyOrder: z.array(z.string()).optional(),
|
|
27
27
|
});
|
|
28
28
|
|
|
29
|
-
export const serviceRequirementSchema = z.
|
|
29
|
+
export const serviceRequirementSchema = z.looseObject({
|
|
30
30
|
id: z.string().min(1).regex(SLUG_REGEX, {
|
|
31
31
|
error: "Must be a valid slug (a-z, 0-9, hyphens, no leading/trailing hyphen)",
|
|
32
32
|
}),
|
|
33
33
|
provider: z.string(),
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
description: z.string().optional(),
|
|
35
|
+
scopes: z.array(z.string()).optional(),
|
|
36
|
+
connectionMode: z.enum(["user", "admin"]).optional(),
|
|
36
37
|
});
|
|
37
38
|
|
|
38
39
|
const slugString = z.string().min(1).regex(SLUG_REGEX, {
|
|
@@ -44,9 +45,9 @@ const semverRangeString = z.string().refine((val) => semver.validRange(val) !==
|
|
|
44
45
|
});
|
|
45
46
|
|
|
46
47
|
const registryDependenciesSchema = z
|
|
47
|
-
.
|
|
48
|
-
skills: z.record(z.string(), semverRangeString).optional()
|
|
49
|
-
extensions: z.record(z.string(), semverRangeString).optional()
|
|
48
|
+
.looseObject({
|
|
49
|
+
skills: z.record(z.string(), semverRangeString).optional(),
|
|
50
|
+
extensions: z.record(z.string(), semverRangeString).optional(),
|
|
50
51
|
})
|
|
51
52
|
.optional();
|
|
52
53
|
|
|
@@ -58,7 +59,7 @@ export const scopedNameRegex = /^@[a-z0-9]([a-z0-9-]*[a-z0-9])?\/[a-z0-9]([a-z0-
|
|
|
58
59
|
export const packageTypeEnum = z.enum(["flow", "skill", "extension"]);
|
|
59
60
|
export type PackageType = z.infer<typeof packageTypeEnum>;
|
|
60
61
|
|
|
61
|
-
export const baseManifestSchema = z.
|
|
62
|
+
export const baseManifestSchema = z.looseObject({
|
|
62
63
|
name: z.string().regex(scopedNameRegex, { error: "Must follow the format @scope/package-name" }),
|
|
63
64
|
version: z.string().refine((v) => semver.valid(v) !== null, {
|
|
64
65
|
error: "Must be a valid semver version",
|
|
@@ -81,14 +82,14 @@ export type FlowJsonSchemaProperty = z.infer<typeof jsonSchemaPropertySchema>;
|
|
|
81
82
|
export type FlowJsonSchemaObject = z.infer<typeof jsonSchemaObjectSchema>;
|
|
82
83
|
|
|
83
84
|
// ─────────────────────────────────────────────
|
|
84
|
-
// Shared flow fields
|
|
85
|
+
// Shared flow fields
|
|
85
86
|
// ─────────────────────────────────────────────
|
|
86
87
|
|
|
87
88
|
const flowSharedFields = {
|
|
88
|
-
requires: z.
|
|
89
|
+
requires: z.looseObject({
|
|
89
90
|
services: z.array(serviceRequirementSchema),
|
|
90
|
-
skills: z.array(slugString).optional()
|
|
91
|
-
extensions: z.array(slugString).optional()
|
|
91
|
+
skills: z.array(slugString).optional(),
|
|
92
|
+
extensions: z.array(slugString).optional(),
|
|
92
93
|
}),
|
|
93
94
|
input: z
|
|
94
95
|
.object({
|
|
@@ -106,7 +107,7 @@ const flowSharedFields = {
|
|
|
106
107
|
})
|
|
107
108
|
.optional(),
|
|
108
109
|
execution: z
|
|
109
|
-
.
|
|
110
|
+
.looseObject({
|
|
110
111
|
timeout: z.number().optional(),
|
|
111
112
|
outputRetries: z.number().min(0).max(5).optional(),
|
|
112
113
|
})
|
|
@@ -154,9 +155,8 @@ export function validateManifest(raw: unknown): ValidateManifestResult {
|
|
|
154
155
|
return { valid: false, errors };
|
|
155
156
|
}
|
|
156
157
|
|
|
157
|
-
// No type field —
|
|
158
|
-
|
|
159
|
-
return validateFlowManifest(raw);
|
|
158
|
+
// No type field — require it explicitly
|
|
159
|
+
return { valid: false, errors: ["type: Required field is missing"] };
|
|
160
160
|
}
|
|
161
161
|
|
|
162
162
|
function validateFlowManifest(raw: unknown): ValidateManifestResult {
|
|
@@ -232,39 +232,6 @@ function countParams(paramStr: string): number {
|
|
|
232
232
|
return count;
|
|
233
233
|
}
|
|
234
234
|
|
|
235
|
-
// ─────────────────────────────────────────────
|
|
236
|
-
// Local flow manifest schema — relaxed for strate (no scoped name, optional version)
|
|
237
|
-
// ─────────────────────────────────────────────
|
|
238
|
-
|
|
239
|
-
export const localFlowManifestSchema = z.looseObject({
|
|
240
|
-
$schema: z.string().optional(),
|
|
241
|
-
schemaVersion: z.string(),
|
|
242
|
-
name: slugString,
|
|
243
|
-
displayName: z.string().min(1),
|
|
244
|
-
description: z.string(),
|
|
245
|
-
author: z.string(),
|
|
246
|
-
license: z.string().optional(),
|
|
247
|
-
tags: z.array(z.string()).optional(),
|
|
248
|
-
type: z.string().optional(),
|
|
249
|
-
version: z.string().optional(),
|
|
250
|
-
...flowSharedFields,
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
export type LocalFlowManifest = z.infer<typeof localFlowManifestSchema>;
|
|
254
|
-
|
|
255
|
-
export function validateLocalFlowManifest(raw: unknown): {
|
|
256
|
-
valid: boolean;
|
|
257
|
-
errors: string[];
|
|
258
|
-
manifest?: unknown;
|
|
259
|
-
} {
|
|
260
|
-
const result = localFlowManifestSchema.safeParse(raw);
|
|
261
|
-
if (result.success) {
|
|
262
|
-
return { valid: true, errors: [], manifest: result.data };
|
|
263
|
-
}
|
|
264
|
-
const errors = result.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`);
|
|
265
|
-
return { valid: false, errors };
|
|
266
|
-
}
|
|
267
|
-
|
|
268
235
|
export function validateExtensionSource(source: string): ExtensionValidationResult {
|
|
269
236
|
const errors: string[] = [];
|
|
270
237
|
const warnings: string[] = [];
|
package/src/naming.ts
CHANGED
|
@@ -16,6 +16,13 @@ export function scopedNameToPackageId(scopedName: string): string {
|
|
|
16
16
|
return `${match[1]}--${match[2]}`;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
/** Parse "@scope/name" into { scope, name } or null if invalid */
|
|
20
|
+
export function parseScopedName(scopedName: string): { scope: string; name: string } | null {
|
|
21
|
+
const match = scopedName.match(/^@([^/]+)\/(.+)$/);
|
|
22
|
+
if (!match) return null;
|
|
23
|
+
return { scope: match[1]!, name: match[2]! };
|
|
24
|
+
}
|
|
25
|
+
|
|
19
26
|
/** Convert strate packageId "scope--name" to "@scope/name", or null for local slugs */
|
|
20
27
|
export function packageIdToScopedName(packageId: string): string | null {
|
|
21
28
|
const idx = packageId.indexOf("--");
|
package/src/zip.ts
CHANGED
|
@@ -133,10 +133,9 @@ export function parsePackageZip(zipBuffer: Uint8Array, maxSize?: number): Parsed
|
|
|
133
133
|
);
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
-
const manifest =
|
|
136
|
+
const manifest = manifestRaw as Record<string, unknown>;
|
|
137
137
|
|
|
138
|
-
|
|
139
|
-
const type = (manifest.type as "flow" | "skill" | "extension") ?? "flow";
|
|
138
|
+
const type = manifest.type as "flow" | "skill" | "extension";
|
|
140
139
|
|
|
141
140
|
// Extract primary content based on type
|
|
142
141
|
let content: string;
|