@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 CHANGED
@@ -1,8 +1,10 @@
1
1
  {
2
2
  "name": "@appstrate/validation",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "type": "module",
5
- "files": ["src"],
5
+ "files": [
6
+ "src"
7
+ ],
6
8
  "exports": {
7
9
  ".": "./src/index.ts",
8
10
  "./zip": "./src/zip.ts",
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.object({
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
- scopes: z.array(z.string()).optional().default([]),
35
- connectionMode: z.enum(["user", "admin"]).optional().default("user"),
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
- .object({
48
- skills: z.record(z.string(), semverRangeString).optional().default({}),
49
- extensions: z.record(z.string(), semverRangeString).optional().default({}),
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.object({
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 — used by both flowManifestSchema and localFlowManifestSchema
85
+ // Shared flow fields
85
86
  // ─────────────────────────────────────────────
86
87
 
87
88
  const flowSharedFields = {
88
- requires: z.object({
89
+ requires: z.looseObject({
89
90
  services: z.array(serviceRequirementSchema),
90
- skills: z.array(slugString).optional().default([]),
91
- extensions: z.array(slugString).optional().default([]),
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
- .object({
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 — try flow manifest validation (backward compat for flow-only manifests
158
- // that don't have name/version/type because they rely on the old format)
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 = validation.manifest as Record<string, unknown>;
136
+ const manifest = manifestRaw as Record<string, unknown>;
137
137
 
138
- // Determine type (fallback "flow" for legacy manifests without type)
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;