@jay-framework/jay-stack-cli 0.18.0 → 0.18.2
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/agent-kit-template/developer/cli-commands.md +2 -0
- package/agent-kit-template/plugin/INSTRUCTIONS.md +1 -1
- package/agent-kit-template/plugin/contracts-guide.md +15 -0
- package/agent-kit-template/plugin/plugin-structure.md +15 -0
- package/agent-kit-template/plugin/validation.md +90 -0
- package/dist/index.js +136 -3
- package/package.json +11 -11
|
@@ -93,6 +93,8 @@ Errors:
|
|
|
93
93
|
1 error(s) found, 7 file(s) valid.
|
|
94
94
|
```
|
|
95
95
|
|
|
96
|
+
Plugins can provide custom validators that run as part of `jay-stack validate`. Plugin findings include a suggestion field with fix instructions. See the plugin [validation.md](../plugin/validation.md) guide.
|
|
97
|
+
|
|
96
98
|
Always run validate after creating or editing jay-html and contract files.
|
|
97
99
|
|
|
98
100
|
## jay-stack params
|
|
@@ -34,7 +34,7 @@ A plugin provides headless components (data + interactions, no UI) that project
|
|
|
34
34
|
| [plugin-routes.md](plugin-routes.md) | Plugin-provided pages: routes, jay-html templates, page components |
|
|
35
35
|
| [seo-guide.md](seo-guide.md) | SEO head tags: title, meta, OG, canonical via phaseOutput |
|
|
36
36
|
| [commands-guide.md](commands-guide.md) | makeCliCommand, .jay-command files, CONSOLE_CONTEXT, jay-stack run |
|
|
37
|
-
| [validation.md](validation.md) | jay-stack validate-plugin
|
|
37
|
+
| [validation.md](validation.md) | jay-stack validate-plugin, writing custom jay-html validators |
|
|
38
38
|
| [dev-server-service.md](dev-server-service.md) | Dev server service API: routes, params, freeze management |
|
|
39
39
|
| `../references/<plugin>/` | Plugin reference data |
|
|
40
40
|
|
|
@@ -190,6 +190,21 @@ name: product-search
|
|
|
190
190
|
description: Product listing with filters, sorting, and pagination. Use for search results and category pages.
|
|
191
191
|
```
|
|
192
192
|
|
|
193
|
+
## Tag Metadata
|
|
194
|
+
|
|
195
|
+
Tags can carry a `meta` field — a free-form key-value map for plugin validators. The framework ignores `meta`; only validators read it.
|
|
196
|
+
|
|
197
|
+
```yaml
|
|
198
|
+
- tag: heroImage
|
|
199
|
+
type: data
|
|
200
|
+
dataType: string
|
|
201
|
+
meta:
|
|
202
|
+
vendor: wix-image
|
|
203
|
+
defaultTransform: w_800,h_400,q_80
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Use `meta` to attach semantic meaning that goes beyond the data type — e.g., marking a `string` tag as a URL that requires specific formatting. See [validation.md](validation.md) for writing validators that consume `meta`.
|
|
207
|
+
|
|
193
208
|
## Validation Rules
|
|
194
209
|
|
|
195
210
|
- Tag names must be unique at each level
|
|
@@ -61,6 +61,11 @@ commands:
|
|
|
61
61
|
- name: sync-catalog
|
|
62
62
|
command: commands/sync-catalog.jay-command
|
|
63
63
|
|
|
64
|
+
validators:
|
|
65
|
+
- name: media-optimization
|
|
66
|
+
handler: ./validators/media-validator
|
|
67
|
+
description: Ensures media URLs use resize parameters
|
|
68
|
+
|
|
64
69
|
setup:
|
|
65
70
|
handler: setup-handler
|
|
66
71
|
references: references-handler
|
|
@@ -185,6 +190,14 @@ Plugin routes are served by the dev server alongside project routes. If a projec
|
|
|
185
190
|
|
|
186
191
|
Commands are CLI operations run via `jay-stack run`. Use `makeCliCommand()` to create handlers with service injection. See [commands-guide.md](commands-guide.md).
|
|
187
192
|
|
|
193
|
+
### Validator Entry Fields
|
|
194
|
+
|
|
195
|
+
- `name` — Validator name (shown in validation output as `plugin-name/validator-name`)
|
|
196
|
+
- `handler` — Relative path to the validator module (must export a `validate` function)
|
|
197
|
+
- `description` — (optional) What this validator checks
|
|
198
|
+
|
|
199
|
+
Validators run during `jay-stack validate` against every parsed jay-html file. The handler module exports a `validate` function that receives a `JayHtmlValidationContext` and returns an array of findings. See [validation.md](validation.md) for implementation details.
|
|
200
|
+
|
|
188
201
|
### Setup Fields
|
|
189
202
|
|
|
190
203
|
- `handler` — Setup handler for `jay-stack setup` (handles config, credentials)
|
|
@@ -210,6 +223,8 @@ my-plugin/
|
|
|
210
223
|
│ │ └── upload-public.jay-command
|
|
211
224
|
│ ├── webhooks/
|
|
212
225
|
│ │ └── on-product-change.ts
|
|
226
|
+
│ ├── validators/
|
|
227
|
+
│ │ └── media-validator.ts
|
|
213
228
|
│ ├── components/
|
|
214
229
|
│ │ ├── product-page.ts
|
|
215
230
|
│ │ └── product-search.ts
|
|
@@ -99,3 +99,93 @@ component: ./lib/components/product-page.ts
|
|
|
99
99
|
# Right
|
|
100
100
|
component: productPage
|
|
101
101
|
```
|
|
102
|
+
|
|
103
|
+
## Plugin Validators
|
|
104
|
+
|
|
105
|
+
Plugins can provide custom jay-html validation rules that run during `jay-stack validate`. Declare validators in `plugin.yaml`:
|
|
106
|
+
|
|
107
|
+
```yaml
|
|
108
|
+
validators:
|
|
109
|
+
- name: media-optimization
|
|
110
|
+
handler: ./validators/media-validator
|
|
111
|
+
description: Ensures media URLs use resize parameters
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Writing a Validator
|
|
115
|
+
|
|
116
|
+
The handler module exports a `validate` function:
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
import type { JayHtmlValidatorFn, JayHtmlValidationFinding } from '@jay-framework/compiler-shared';
|
|
120
|
+
|
|
121
|
+
export const validate: JayHtmlValidatorFn = (ctx) => {
|
|
122
|
+
const findings: JayHtmlValidationFinding[] = [];
|
|
123
|
+
|
|
124
|
+
// ctx.body — parsed DOM tree (HTMLElement from node-html-parser)
|
|
125
|
+
// ctx.filePath — relative path to the jay-html file
|
|
126
|
+
// ctx.contract — page contract (if any), with tags including meta
|
|
127
|
+
// ctx.headlessImports — headless components used in this file
|
|
128
|
+
// ctx.projectRoot — absolute project root path
|
|
129
|
+
|
|
130
|
+
return findings;
|
|
131
|
+
};
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Each finding has:
|
|
135
|
+
|
|
136
|
+
- `severity` — `'error'` (fails validation) or `'warning'`
|
|
137
|
+
- `message` — what's wrong
|
|
138
|
+
- `suggestion` — how to fix it (shown to agents and developers)
|
|
139
|
+
- `element` — (optional) which element
|
|
140
|
+
- `attribute` — (optional) which attribute
|
|
141
|
+
|
|
142
|
+
### Validator Utilities
|
|
143
|
+
|
|
144
|
+
- **`parseTemplateParts(value)`** — split `"{url}/v1/fit/w_300/file.jpg"` into binding and static parts (import from `@jay-framework/compiler-jay-html`)
|
|
145
|
+
- **`walkElements(root, ctx, visitor)`** — depth-first traversal tracking data scope through `forEach` and `<jay:component>` boundaries (import from `@jay-framework/compiler-shared`)
|
|
146
|
+
- **`resolveBinding(path, scope)`** — resolve a binding path to its contract tag (including `meta`) (import from `@jay-framework/compiler-shared`)
|
|
147
|
+
|
|
148
|
+
### Contract Tag `meta`
|
|
149
|
+
|
|
150
|
+
Contract tags can carry a `meta` field — arbitrary key-value metadata that validators read:
|
|
151
|
+
|
|
152
|
+
```yaml
|
|
153
|
+
tags:
|
|
154
|
+
- tag: imageUrl
|
|
155
|
+
type: data
|
|
156
|
+
dataType: string
|
|
157
|
+
meta:
|
|
158
|
+
vendor: wix-image
|
|
159
|
+
defaultTransform: w_300,h_200,q_80
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Validators use `resolveBinding` to find the tag and inspect `meta`:
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
import { walkElements, resolveBinding } from '@jay-framework/compiler-shared';
|
|
166
|
+
import { parseTemplateParts } from '@jay-framework/compiler-jay-html';
|
|
167
|
+
|
|
168
|
+
export const validate: JayHtmlValidatorFn = (ctx) => {
|
|
169
|
+
const findings: JayHtmlValidationFinding[] = [];
|
|
170
|
+
|
|
171
|
+
walkElements(ctx.body, ctx, (el, scope) => {
|
|
172
|
+
if (el.rawTagName !== 'img') return;
|
|
173
|
+
const src = el.getAttribute('src');
|
|
174
|
+
if (!src) return;
|
|
175
|
+
|
|
176
|
+
for (const part of parseTemplateParts(src)) {
|
|
177
|
+
if (part.kind !== 'binding') continue;
|
|
178
|
+
const resolved = resolveBinding(part.value, scope);
|
|
179
|
+
if (resolved.tag?.meta?.vendor !== 'wix-image') continue;
|
|
180
|
+
|
|
181
|
+
findings.push({
|
|
182
|
+
severity: 'warning',
|
|
183
|
+
message: `Image binding {${part.value}} may need resize parameters`,
|
|
184
|
+
suggestion: 'Add /v1/fit/w_{WIDTH},h_{HEIGHT},q_80/file.jpg after the binding',
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
return findings;
|
|
190
|
+
};
|
|
191
|
+
```
|
package/dist/index.js
CHANGED
|
@@ -10,7 +10,7 @@ import YAML from "yaml";
|
|
|
10
10
|
import { getLogger, setDevLogger, createDevLogger } from "@jay-framework/logger";
|
|
11
11
|
import { parseJayFile, JAY_IMPORT_RESOLVER, generateElementDefinitionFile, ContractTagType, parseContract, generateElementFile, generateServerElementFile, htmlElementTagNameMap } from "@jay-framework/compiler-jay-html";
|
|
12
12
|
import { JAY_CONTRACT_EXTENSION, JAY_EXTENSION, resolvePluginManifest, LOCAL_PLUGIN_PATH, JayAtomicType, JayEnumType, loadPluginManifest, RuntimeMode, GenerateTarget } from "@jay-framework/compiler-shared";
|
|
13
|
-
import {
|
|
13
|
+
import { scanPlugins as scanPlugins$1, listContracts, materializeContracts } from "@jay-framework/stack-server-runtime";
|
|
14
14
|
import { listContracts as listContracts2, materializeContracts as materializeContracts2 } from "@jay-framework/stack-server-runtime";
|
|
15
15
|
import { Command } from "commander";
|
|
16
16
|
import chalk from "chalk";
|
|
@@ -3358,6 +3358,46 @@ async function validateSchema(context, result) {
|
|
|
3358
3358
|
});
|
|
3359
3359
|
}
|
|
3360
3360
|
}
|
|
3361
|
+
if (manifest.validators) {
|
|
3362
|
+
if (!Array.isArray(manifest.validators)) {
|
|
3363
|
+
result.errors.push({
|
|
3364
|
+
type: "schema",
|
|
3365
|
+
message: 'Field "validators" must be an array',
|
|
3366
|
+
location: "plugin.yaml"
|
|
3367
|
+
});
|
|
3368
|
+
} else {
|
|
3369
|
+
manifest.validators.forEach((validator, index) => {
|
|
3370
|
+
if (!validator.name) {
|
|
3371
|
+
result.errors.push({
|
|
3372
|
+
type: "schema",
|
|
3373
|
+
message: `Validator at index ${index} is missing "name" field`,
|
|
3374
|
+
location: "plugin.yaml"
|
|
3375
|
+
});
|
|
3376
|
+
}
|
|
3377
|
+
if (!validator.handler) {
|
|
3378
|
+
result.errors.push({
|
|
3379
|
+
type: "schema",
|
|
3380
|
+
message: `Validator "${validator.name || index}" is missing "handler" field`,
|
|
3381
|
+
location: "plugin.yaml",
|
|
3382
|
+
suggestion: "Specify the relative path to the validator handler module"
|
|
3383
|
+
});
|
|
3384
|
+
}
|
|
3385
|
+
if (validator.handler && !context.isNpmPackage) {
|
|
3386
|
+
const handlerPath = path.join(context.pluginPath, validator.handler);
|
|
3387
|
+
const extensions = ["", ".ts", ".js", "/index.ts", "/index.js"];
|
|
3388
|
+
const found = extensions.some((ext) => fs.existsSync(handlerPath + ext));
|
|
3389
|
+
if (!found) {
|
|
3390
|
+
result.errors.push({
|
|
3391
|
+
type: "file-missing",
|
|
3392
|
+
message: `Validator "${validator.name}" handler not found: ${validator.handler}`,
|
|
3393
|
+
location: "plugin.yaml validators",
|
|
3394
|
+
suggestion: `Create the validator handler at ${handlerPath}.ts`
|
|
3395
|
+
});
|
|
3396
|
+
}
|
|
3397
|
+
}
|
|
3398
|
+
});
|
|
3399
|
+
}
|
|
3400
|
+
}
|
|
3361
3401
|
}
|
|
3362
3402
|
function resolveContractFile(contractSpec, context) {
|
|
3363
3403
|
if (context.isNpmPackage) {
|
|
@@ -4226,6 +4266,88 @@ function checkHeadlessInstanceProps(jayHtml, file) {
|
|
|
4226
4266
|
walkElement(jayHtml.body);
|
|
4227
4267
|
return warnings;
|
|
4228
4268
|
}
|
|
4269
|
+
async function runPluginValidators(projectRoot, parsedFiles, errors, warnings) {
|
|
4270
|
+
const scannedPlugins = await scanPlugins$1({ projectRoot });
|
|
4271
|
+
for (const [, plugin] of scannedPlugins) {
|
|
4272
|
+
if (!plugin.manifest.validators)
|
|
4273
|
+
continue;
|
|
4274
|
+
for (const validatorDef of plugin.manifest.validators) {
|
|
4275
|
+
let validatorFn;
|
|
4276
|
+
try {
|
|
4277
|
+
const handlerPath = path.resolve(plugin.pluginPath, validatorDef.handler);
|
|
4278
|
+
const handlerModule = await import(handlerPath);
|
|
4279
|
+
validatorFn = handlerModule.validate;
|
|
4280
|
+
if (typeof validatorFn !== "function") {
|
|
4281
|
+
errors.push({
|
|
4282
|
+
file: `plugin:${plugin.name}`,
|
|
4283
|
+
message: `Validator "${validatorDef.name}" handler does not export a "validate" function`,
|
|
4284
|
+
stage: "plugin"
|
|
4285
|
+
});
|
|
4286
|
+
continue;
|
|
4287
|
+
}
|
|
4288
|
+
} catch (loadErr) {
|
|
4289
|
+
errors.push({
|
|
4290
|
+
file: `plugin:${plugin.name}`,
|
|
4291
|
+
message: `Failed to load validator "${validatorDef.name}": ${loadErr.message}`,
|
|
4292
|
+
stage: "plugin"
|
|
4293
|
+
});
|
|
4294
|
+
continue;
|
|
4295
|
+
}
|
|
4296
|
+
const source = `${plugin.name}/${validatorDef.name}`;
|
|
4297
|
+
for (const { relativePath, parsed } of parsedFiles) {
|
|
4298
|
+
const ctx = {
|
|
4299
|
+
filePath: relativePath,
|
|
4300
|
+
body: parsed.body,
|
|
4301
|
+
contract: parsed.contract ? {
|
|
4302
|
+
name: parsed.contract.name,
|
|
4303
|
+
tags: parsed.contract.tags,
|
|
4304
|
+
props: parsed.contract.props,
|
|
4305
|
+
params: parsed.contract.params
|
|
4306
|
+
} : void 0,
|
|
4307
|
+
headlessImports: parsed.headlessImports.map((imp) => ({
|
|
4308
|
+
key: imp.key,
|
|
4309
|
+
contractName: imp.contractName,
|
|
4310
|
+
contract: imp.contract ? {
|
|
4311
|
+
name: imp.contract.name,
|
|
4312
|
+
tags: imp.contract.tags,
|
|
4313
|
+
props: imp.contract.props,
|
|
4314
|
+
params: imp.contract.params
|
|
4315
|
+
} : void 0
|
|
4316
|
+
})),
|
|
4317
|
+
projectRoot
|
|
4318
|
+
};
|
|
4319
|
+
try {
|
|
4320
|
+
const findings = await validatorFn(ctx);
|
|
4321
|
+
for (const finding of findings) {
|
|
4322
|
+
if (finding.severity === "error") {
|
|
4323
|
+
errors.push({
|
|
4324
|
+
file: relativePath,
|
|
4325
|
+
message: finding.message,
|
|
4326
|
+
stage: "plugin",
|
|
4327
|
+
source,
|
|
4328
|
+
suggestion: finding.suggestion
|
|
4329
|
+
});
|
|
4330
|
+
} else {
|
|
4331
|
+
warnings.push({
|
|
4332
|
+
file: relativePath,
|
|
4333
|
+
message: finding.message,
|
|
4334
|
+
source,
|
|
4335
|
+
suggestion: finding.suggestion
|
|
4336
|
+
});
|
|
4337
|
+
}
|
|
4338
|
+
}
|
|
4339
|
+
} catch (runErr) {
|
|
4340
|
+
errors.push({
|
|
4341
|
+
file: relativePath,
|
|
4342
|
+
message: `Validator "${source}" threw: ${runErr.message}`,
|
|
4343
|
+
stage: "plugin",
|
|
4344
|
+
source
|
|
4345
|
+
});
|
|
4346
|
+
}
|
|
4347
|
+
}
|
|
4348
|
+
}
|
|
4349
|
+
}
|
|
4350
|
+
}
|
|
4229
4351
|
async function validateJayFiles(options = {}) {
|
|
4230
4352
|
const config = loadConfig();
|
|
4231
4353
|
const resolvedConfig = getConfigWithDefaults(config);
|
|
@@ -4234,6 +4356,7 @@ async function validateJayFiles(options = {}) {
|
|
|
4234
4356
|
const errors = [];
|
|
4235
4357
|
const warnings = [];
|
|
4236
4358
|
const coverage = [];
|
|
4359
|
+
const parsedFiles = [];
|
|
4237
4360
|
const jayHtmlFiles = await findJayFiles(scanDir);
|
|
4238
4361
|
const contractFiles = await findContractFiles(scanDir);
|
|
4239
4362
|
if (options.verbose) {
|
|
@@ -4299,6 +4422,7 @@ async function validateJayFiles(options = {}) {
|
|
|
4299
4422
|
}
|
|
4300
4423
|
continue;
|
|
4301
4424
|
}
|
|
4425
|
+
parsedFiles.push({ relativePath, parsed: parsedFile.val });
|
|
4302
4426
|
const routeParamWarnings = checkRouteParams(parsedFile.val, jayFile, scanDir, content);
|
|
4303
4427
|
for (const msg of routeParamWarnings) {
|
|
4304
4428
|
warnings.push({ file: relativePath, message: msg });
|
|
@@ -4363,6 +4487,7 @@ async function validateJayFiles(options = {}) {
|
|
|
4363
4487
|
}
|
|
4364
4488
|
}
|
|
4365
4489
|
}
|
|
4490
|
+
await runPluginValidators(projectRoot, parsedFiles, errors, warnings);
|
|
4366
4491
|
return {
|
|
4367
4492
|
valid: errors.length === 0,
|
|
4368
4493
|
jayHtmlFilesScanned: jayHtmlFiles.length,
|
|
@@ -4389,8 +4514,12 @@ function printJayValidationResult(result, options) {
|
|
|
4389
4514
|
logger.important(chalk.red("❌ Jay Stack validation failed\n"));
|
|
4390
4515
|
logger.important("Errors:");
|
|
4391
4516
|
for (const error of result.errors) {
|
|
4517
|
+
const prefix = error.source ? `[${error.source}] ` : "";
|
|
4392
4518
|
logger.important(chalk.red(` ❌ ${error.file}`));
|
|
4393
|
-
logger.important(chalk.gray(` ${error.message}`));
|
|
4519
|
+
logger.important(chalk.gray(` ${prefix}${error.message}`));
|
|
4520
|
+
if (error.suggestion) {
|
|
4521
|
+
logger.important(chalk.blue(` Suggestion: ${error.suggestion}`));
|
|
4522
|
+
}
|
|
4394
4523
|
logger.important("");
|
|
4395
4524
|
}
|
|
4396
4525
|
const validFiles = result.jayHtmlFilesScanned + result.contractFilesScanned - result.errors.length;
|
|
@@ -4402,8 +4531,12 @@ function printJayValidationResult(result, options) {
|
|
|
4402
4531
|
logger.important("");
|
|
4403
4532
|
logger.important(chalk.yellow("Warnings:"));
|
|
4404
4533
|
for (const warning of result.warnings) {
|
|
4534
|
+
const prefix = warning.source ? `[${warning.source}] ` : "";
|
|
4405
4535
|
logger.important(chalk.yellow(` ⚠ ${warning.file}`));
|
|
4406
|
-
logger.important(chalk.gray(` ${warning.message}`));
|
|
4536
|
+
logger.important(chalk.gray(` ${prefix}${warning.message}`));
|
|
4537
|
+
if (warning.suggestion) {
|
|
4538
|
+
logger.important(chalk.blue(` Suggestion: ${warning.suggestion}`));
|
|
4539
|
+
}
|
|
4407
4540
|
logger.important("");
|
|
4408
4541
|
}
|
|
4409
4542
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jay-framework/jay-stack-cli",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.2",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -24,15 +24,15 @@
|
|
|
24
24
|
"test:watch": "vitest"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@jay-framework/compiler-jay-html": "^0.18.
|
|
28
|
-
"@jay-framework/compiler-shared": "^0.18.
|
|
29
|
-
"@jay-framework/dev-server": "^0.18.
|
|
30
|
-
"@jay-framework/editor-server": "^0.18.
|
|
31
|
-
"@jay-framework/fullstack-component": "^0.18.
|
|
32
|
-
"@jay-framework/logger": "^0.18.
|
|
33
|
-
"@jay-framework/plugin-validator": "^0.18.
|
|
34
|
-
"@jay-framework/production-server": "^0.18.
|
|
35
|
-
"@jay-framework/stack-server-runtime": "^0.18.
|
|
27
|
+
"@jay-framework/compiler-jay-html": "^0.18.2",
|
|
28
|
+
"@jay-framework/compiler-shared": "^0.18.2",
|
|
29
|
+
"@jay-framework/dev-server": "^0.18.2",
|
|
30
|
+
"@jay-framework/editor-server": "^0.18.2",
|
|
31
|
+
"@jay-framework/fullstack-component": "^0.18.2",
|
|
32
|
+
"@jay-framework/logger": "^0.18.2",
|
|
33
|
+
"@jay-framework/plugin-validator": "^0.18.2",
|
|
34
|
+
"@jay-framework/production-server": "^0.18.2",
|
|
35
|
+
"@jay-framework/stack-server-runtime": "^0.18.2",
|
|
36
36
|
"chalk": "^4.1.2",
|
|
37
37
|
"commander": "^14.0.0",
|
|
38
38
|
"express": "^5.0.1",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"yaml": "^2.3.4"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
|
-
"@jay-framework/dev-environment": "^0.18.
|
|
46
|
+
"@jay-framework/dev-environment": "^0.18.2",
|
|
47
47
|
"@types/express": "^5.0.2",
|
|
48
48
|
"@types/node": "^22.15.21",
|
|
49
49
|
"nodemon": "^3.0.3",
|