Skip to content

Commit d3cc0e7

Browse files
committed
feat: Added option provide a zod schema instead of json schema
1 parent ce91f07 commit d3cc0e7

10 files changed

+273
-23
lines changed

package-lock.json

Lines changed: 13 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "codify-plugin-lib",
3-
"version": "1.0.182-beta27",
3+
"version": "1.0.182-beta31",
44
"description": "Library plugin library",
55
"main": "dist/index.js",
66
"typings": "dist/index.d.ts",
@@ -26,7 +26,8 @@
2626
"lodash.isequal": "^4.5.0",
2727
"nanoid": "^5.0.9",
2828
"strip-ansi": "^7.1.0",
29-
"uuid": "^10.0.0"
29+
"uuid": "^10.0.0",
30+
"zod": "4.1.13"
3031
},
3132
"devDependencies": {
3233
"@apidevtools/json-schema-ref-parser": "^11.7.2",

src/plugin/plugin.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { spy } from 'sinon';
77
import { ResourceSettings } from '../resource/resource-settings.js';
88
import { TestConfig, TestStatefulParameter } from '../utils/test-utils.test.js';
99
import { getPty } from '../pty/index.js';
10+
import { z } from 'zod';
1011

1112
interface TestConfig extends StringIndexedObject {
1213
propA: string;
@@ -170,6 +171,36 @@ describe('Plugin tests', () => {
170171
})
171172
})
172173

174+
it('Can get resource info (zod schema)', async () => {
175+
const schema = z
176+
.object({
177+
plugins: z
178+
.array(z.string())
179+
.describe(
180+
'Asdf plugins to install. See: https://github.com/asdf-community for a full list'
181+
)
182+
})
183+
.strict()
184+
185+
const resource = new class extends TestResource {
186+
getSettings(): ResourceSettings<TestConfig> {
187+
return {
188+
id: 'typeId',
189+
operatingSystems: [OS.Darwin],
190+
schema,
191+
}
192+
}
193+
}
194+
const testPlugin = Plugin.create('testPlugin', [resource as any])
195+
196+
const resourceInfo = await testPlugin.getResourceInfo({ type: 'typeId' })
197+
expect(resourceInfo.import).toMatchObject({
198+
requiredParameters: [
199+
'plugins'
200+
]
201+
})
202+
})
203+
173204
it('Get resource info to default import to the one specified in the resource settings', async () => {
174205
const schema = {
175206
'$schema': 'http://json-schema.org/draft-07/schema',

src/plugin/plugin.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ export class Plugin {
8989

9090
const resource = this.resourceControllers.get(data.type)!;
9191

92-
const schema = resource.settings.schema as JSONSchemaType<any> | undefined;
92+
const schema = resource.parsedSettings.schema as JSONSchemaType<any> | undefined;
9393
const requiredPropertyNames = (
9494
resource.settings.importAndDestroy?.requiredParameters
9595
?? (typeof resource.settings.allowMultiple === 'object' ? resource.settings.allowMultiple.identifyingParameters : null)

src/resource/parsed-resource-settings.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { describe, expect, it } from 'vitest';
22
import { ResourceSettings } from './resource-settings.js';
33
import { ParsedResourceSettings } from './parsed-resource-settings.js';
44
import { TestConfig } from '../utils/test-utils.test.js';
5+
import { z } from 'zod';
6+
import { OS } from 'codify-schemas';
57

68
describe('Resource options parser tests', () => {
79
it('Parses default values from options', () => {
@@ -159,4 +161,26 @@ describe('Resource options parser tests', () => {
159161

160162
expect(() => new ParsedResourceSettings(option)).toThrowError()
161163
})
164+
165+
it('Can handle a zod schema', () => {
166+
167+
const schema = z.object({
168+
propA: z.string(),
169+
repository: z.string(),
170+
})
171+
172+
const option: ResourceSettings<z.infer<typeof schema>> = {
173+
id: 'typeId',
174+
operatingSystems: [OS.Darwin],
175+
schema,
176+
importAndDestroy: {
177+
defaultRefreshValues: {
178+
repository: 'abc'
179+
}
180+
}
181+
}
182+
183+
console.log(new ParsedResourceSettings(option))
184+
185+
})
162186
})

src/resource/parsed-resource-settings.ts

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
import { JSONSchemaType } from 'ajv';
22
import { OS, StringIndexedObject } from 'codify-schemas';
3+
import { ZodObject, z } from 'zod';
34

45
import { StatefulParameterController } from '../stateful-parameter/stateful-parameter-controller.js';
56
import {
67
ArrayParameterSetting,
78
DefaultParameterSetting,
89
InputTransformation,
910
ParameterSetting,
11+
ResourceSettings,
12+
StatefulParameterSetting,
1013
resolveElementEqualsFn,
1114
resolveEqualsFn,
1215
resolveMatcher,
13-
resolveParameterTransformFn,
14-
ResourceSettings,
15-
StatefulParameterSetting
16+
resolveParameterTransformFn
1617
} from './resource-settings.js';
1718

1819
export interface ParsedStatefulParameterSetting extends DefaultParameterSetting {
@@ -29,18 +30,21 @@ export type ParsedArrayParameterSetting = {
2930

3031
export type ParsedParameterSetting =
3132
{
32-
isEqual: (desired: unknown, current: unknown) => boolean;
33+
isEqual: (desired: unknown, current: unknown) => boolean;
3334
} & (DefaultParameterSetting
3435
| ParsedArrayParameterSetting
3536
| ParsedStatefulParameterSetting)
3637

3738
export class ParsedResourceSettings<T extends StringIndexedObject> implements ResourceSettings<T> {
3839
private cache = new Map<string, unknown>();
3940
id!: string;
41+
description?: string;
42+
4043
schema?: Partial<JSONSchemaType<T | any>>;
4144
allowMultiple?: {
45+
identifyingParameters?: string[];
4246
matcher?: (desired: Partial<T>, current: Partial<T>) => boolean;
43-
requiredParameters?: string[]
47+
findAllParameters?: () => Promise<Array<Partial<T>>>
4448
} | boolean;
4549

4650
removeStatefulParametersBeforeDestroy?: boolean | undefined;
@@ -54,10 +58,22 @@ export class ParsedResourceSettings<T extends StringIndexedObject> implements Re
5458

5559
constructor(settings: ResourceSettings<T>) {
5660
this.settings = settings;
61+
const { parameterSettings, schema, ...rest } = settings;
5762

58-
const { parameterSettings, ...rest } = settings;
5963
Object.assign(this, rest);
6064

65+
if (schema) {
66+
this.schema = schema instanceof ZodObject
67+
? z.toJSONSchema(schema.strict(), {
68+
target: 'draft-7',
69+
override(ctx) {
70+
ctx.jsonSchema.title = settings.id;
71+
ctx.jsonSchema.description = settings.description ?? `${settings.id} resource. Can be used to manage ${settings.id}`;
72+
}
73+
}) as JSONSchemaType<T>
74+
: schema;
75+
}
76+
6177
this.validateSettings();
6278
}
6379

@@ -199,7 +215,7 @@ export class ParsedResourceSettings<T extends StringIndexedObject> implements Re
199215
throw new Error(`Resource: ${this.id}. Stateful parameters are not allowed to be identifying parameters for allowMultiple.`)
200216
}
201217

202-
const schema = this.settings.schema as JSONSchemaType<any>;
218+
const schema = this.schema as JSONSchemaType<any>;
203219
if (!this.settings.importAndDestroy && (schema?.oneOf
204220
&& Array.isArray(schema.oneOf)
205221
&& schema.oneOf.some((s) => s.required)

src/resource/resource-controller.test.ts

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { tildify, untildify } from '../utils/functions.js';
1111
import { ArrayStatefulParameter, StatefulParameter } from '../stateful-parameter/stateful-parameter.js';
1212
import { Plan } from '../plan/plan.js';
1313
import os from 'node:os';
14+
import { z } from 'zod';
1415

1516
describe('Resource tests', () => {
1617

@@ -952,4 +953,129 @@ describe('Resource tests', () => {
952953

953954
process.env = oldProcessEnv;
954955
})
956+
957+
it('Can import and return all of the imported parameters (zod schema)', async () => {
958+
const schema = z.object({
959+
path: z
960+
.string()
961+
.describe(
962+
'A list of paths to add to the PATH environment variable'
963+
),
964+
paths: z
965+
.array(z.string())
966+
.describe(
967+
'A list of paths to add to the PATH environment variable'
968+
),
969+
prepend: z
970+
.boolean()
971+
.describe(
972+
'Whether to prepend the paths to the PATH environment variable'
973+
),
974+
declarationsOnly: z
975+
.boolean()
976+
.describe(
977+
'Whether to only declare the paths in the PATH environment variable'
978+
),
979+
})
980+
981+
const resource = new class extends TestResource {
982+
getSettings(): ResourceSettings<any> {
983+
return {
984+
id: 'path',
985+
schema,
986+
operatingSystems: [OS.Darwin],
987+
parameterSettings: {
988+
path: { type: 'directory' },
989+
paths: { canModify: true, type: 'array', itemType: 'directory' },
990+
prepend: { default: false, setting: true },
991+
declarationsOnly: { default: false, setting: true },
992+
},
993+
importAndDestroy: {
994+
refreshMapper: (input, context) => {
995+
if (Object.keys(input).length === 0) {
996+
return { paths: [], declarationsOnly: true };
997+
}
998+
999+
return input;
1000+
}
1001+
},
1002+
allowMultiple: {
1003+
matcher: (desired, current) => {
1004+
if (desired.path) {
1005+
return desired.path === current.path;
1006+
}
1007+
1008+
const currentPaths = new Set(current.paths)
1009+
return desired.paths?.some((p) => currentPaths.has(p));
1010+
}
1011+
}
1012+
}
1013+
}
1014+
1015+
async refresh(parameters: Partial<TestConfig>): Promise<Partial<TestConfig> | null> {
1016+
return {
1017+
paths: [
1018+
`${os.homedir()}/.pyenv/bin`,
1019+
`${os.homedir()}/.bun/bin`,
1020+
`${os.homedir()}/.deno/bin`,
1021+
`${os.homedir()}/.jenv/bin`,
1022+
`${os.homedir()}/a/random/path`,
1023+
`${os.homedir()}/.nvm/.bin/2`,
1024+
`${os.homedir()}/.nvm/.bin/3`
1025+
]
1026+
}
1027+
}
1028+
}
1029+
1030+
const oldProcessEnv = structuredClone(process.env);
1031+
1032+
process.env['PYENV_ROOT'] = `${os.homedir()}/.pyenv`
1033+
process.env['BUN_INSTALL'] = `${os.homedir()}/.bun`
1034+
process.env['DENO_INSTALL'] = `${os.homedir()}/.deno`
1035+
process.env['JENV'] = `${os.homedir()}/.jenv`
1036+
process.env['NVM_DIR'] = `${os.homedir()}/.nvm`
1037+
1038+
const controller = new ResourceController(resource);
1039+
const importResult1 = await controller.import({ type: 'path' }, {});
1040+
expect(importResult1).toMatchObject([
1041+
{
1042+
'core': {
1043+
'type': 'path'
1044+
},
1045+
'parameters': {
1046+
'paths': [
1047+
'$PYENV_ROOT/bin',
1048+
'$BUN_INSTALL/bin',
1049+
'$DENO_INSTALL/bin',
1050+
'$JENV/bin',
1051+
'~/a/random/path',
1052+
'$NVM_DIR/.bin/2',
1053+
'$NVM_DIR/.bin/3'
1054+
]
1055+
}
1056+
}
1057+
])
1058+
1059+
const importResult2 = await controller.import({ type: 'path' }, { paths: ['$PYENV_ROOT/bin', '$BUN_INSTALL/bin'] });
1060+
expect(importResult2).toMatchObject([
1061+
{
1062+
'core': {
1063+
'type': 'path'
1064+
},
1065+
'parameters': {
1066+
'paths': [
1067+
'$PYENV_ROOT/bin',
1068+
'$BUN_INSTALL/bin',
1069+
'$DENO_INSTALL/bin',
1070+
'$JENV/bin',
1071+
'~/a/random/path',
1072+
'$NVM_DIR/.bin/2',
1073+
'$NVM_DIR/.bin/3'
1074+
]
1075+
}
1076+
}
1077+
])
1078+
1079+
process.env = oldProcessEnv;
1080+
})
9551081
});

src/resource/resource-controller.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,18 +36,17 @@ export class ResourceController<T extends StringIndexedObject> {
3636

3737
this.typeId = this.settings.id;
3838
this.dependencies = this.settings.dependencies ?? [];
39+
this.parsedSettings = new ParsedResourceSettings<T>(this.settings);
3940

40-
if (this.settings.schema) {
41+
if (this.parsedSettings.schema) {
4142
this.ajv = new Ajv({
4243
allErrors: true,
4344
strict: true,
4445
strictRequired: false,
4546
allowUnionTypes: true
4647
})
47-
this.schemaValidator = this.ajv.compile(this.settings.schema);
48+
this.schemaValidator = this.ajv.compile(this.parsedSettings.schema);
4849
}
49-
50-
this.parsedSettings = new ParsedResourceSettings<T>(this.settings);
5150
}
5251

5352
async initialize(): Promise<void> {
@@ -526,8 +525,8 @@ ${JSON.stringify(refresh, null, 2)}
526525
}
527526

528527
private getAllParameterKeys(): string[] {
529-
return this.settings.schema
530-
? Object.keys((this.settings.schema as any)?.properties)
528+
return this.parsedSettings.schema
529+
? Object.keys((this.parsedSettings.schema as any)?.properties)
531530
: Object.keys(this.parsedSettings.parameterSettings);
532531
}
533532

0 commit comments

Comments
 (0)