@appstrate/validation 1.1.1 → 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,6 +1,6 @@
1
1
  {
2
2
  "name": "@appstrate/validation",
3
- "version": "1.1.1",
3
+ "version": "1.2.0",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "src"
package/src/index.ts CHANGED
@@ -26,14 +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
34
  description: z.string().optional(),
35
- scopes: z.array(z.string()).optional().default([]),
36
- connectionMode: z.enum(["user", "admin"]).optional().default("user"),
35
+ scopes: z.array(z.string()).optional(),
36
+ connectionMode: z.enum(["user", "admin"]).optional(),
37
37
  });
38
38
 
39
39
  const slugString = z.string().min(1).regex(SLUG_REGEX, {
@@ -45,9 +45,9 @@ const semverRangeString = z.string().refine((val) => semver.validRange(val) !==
45
45
  });
46
46
 
47
47
  const registryDependenciesSchema = z
48
- .object({
49
- skills: z.record(z.string(), semverRangeString).optional().default({}),
50
- 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(),
51
51
  })
52
52
  .optional();
53
53
 
@@ -59,7 +59,7 @@ export const scopedNameRegex = /^@[a-z0-9]([a-z0-9-]*[a-z0-9])?\/[a-z0-9]([a-z0-
59
59
  export const packageTypeEnum = z.enum(["flow", "skill", "extension"]);
60
60
  export type PackageType = z.infer<typeof packageTypeEnum>;
61
61
 
62
- export const baseManifestSchema = z.object({
62
+ export const baseManifestSchema = z.looseObject({
63
63
  name: z.string().regex(scopedNameRegex, { error: "Must follow the format @scope/package-name" }),
64
64
  version: z.string().refine((v) => semver.valid(v) !== null, {
65
65
  error: "Must be a valid semver version",
@@ -82,14 +82,14 @@ export type FlowJsonSchemaProperty = z.infer<typeof jsonSchemaPropertySchema>;
82
82
  export type FlowJsonSchemaObject = z.infer<typeof jsonSchemaObjectSchema>;
83
83
 
84
84
  // ─────────────────────────────────────────────
85
- // Shared flow fields — used by both flowManifestSchema and localFlowManifestSchema
85
+ // Shared flow fields
86
86
  // ─────────────────────────────────────────────
87
87
 
88
88
  const flowSharedFields = {
89
- requires: z.object({
89
+ requires: z.looseObject({
90
90
  services: z.array(serviceRequirementSchema),
91
- skills: z.array(slugString).optional().default([]),
92
- extensions: z.array(slugString).optional().default([]),
91
+ skills: z.array(slugString).optional(),
92
+ extensions: z.array(slugString).optional(),
93
93
  }),
94
94
  input: z
95
95
  .object({
@@ -107,7 +107,7 @@ const flowSharedFields = {
107
107
  })
108
108
  .optional(),
109
109
  execution: z
110
- .object({
110
+ .looseObject({
111
111
  timeout: z.number().optional(),
112
112
  outputRetries: z.number().min(0).max(5).optional(),
113
113
  })
@@ -155,9 +155,8 @@ export function validateManifest(raw: unknown): ValidateManifestResult {
155
155
  return { valid: false, errors };
156
156
  }
157
157
 
158
- // No type field — try flow manifest validation (backward compat for flow-only manifests
159
- // that don't have name/version/type because they rely on the old format)
160
- return validateFlowManifest(raw);
158
+ // No type field — require it explicitly
159
+ return { valid: false, errors: ["type: Required field is missing"] };
161
160
  }
162
161
 
163
162
  function validateFlowManifest(raw: unknown): ValidateManifestResult {
@@ -233,39 +232,6 @@ function countParams(paramStr: string): number {
233
232
  return count;
234
233
  }
235
234
 
236
- // ─────────────────────────────────────────────
237
- // Local flow manifest schema — relaxed for strate (no scoped name, optional version)
238
- // ─────────────────────────────────────────────
239
-
240
- export const localFlowManifestSchema = z.looseObject({
241
- $schema: z.string().optional(),
242
- schemaVersion: z.string(),
243
- name: slugString,
244
- displayName: z.string().min(1),
245
- description: z.string(),
246
- author: z.string(),
247
- license: z.string().optional(),
248
- tags: z.array(z.string()).optional(),
249
- type: z.string().optional(),
250
- version: z.string().optional(),
251
- ...flowSharedFields,
252
- });
253
-
254
- export type LocalFlowManifest = z.infer<typeof localFlowManifestSchema>;
255
-
256
- export function validateLocalFlowManifest(raw: unknown): {
257
- valid: boolean;
258
- errors: string[];
259
- manifest?: unknown;
260
- } {
261
- const result = localFlowManifestSchema.safeParse(raw);
262
- if (result.success) {
263
- return { valid: true, errors: [], manifest: result.data };
264
- }
265
- const errors = result.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`);
266
- return { valid: false, errors };
267
- }
268
-
269
235
  export function validateExtensionSource(source: string): ExtensionValidationResult {
270
236
  const errors: string[] = [];
271
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;