mirror of
https://github.com/renovatebot/renovate.git
synced 2026-10-31 17:51:49 +00:00
8be9e6bb6c
The zod v4 migration made compilerOptions fail validation when jsxImportSource/jsxImportSourceTypes keys were absent, aborting Deno dependency extraction.
355 lines
9.5 KiB
TypeScript
355 lines
9.5 KiB
TypeScript
import validateNpmPackageName from 'validate-npm-package-name';
|
|
import { z } from 'zod/v4';
|
|
import { regEx } from '../../../util/regex.ts';
|
|
import {
|
|
Json,
|
|
Jsonc,
|
|
LooseArray,
|
|
LooseRecord,
|
|
} from '../../../util/schema-utils/index.ts';
|
|
import { joinUrlParts } from '../../../util/url.ts';
|
|
import { extractJsrPackageName } from '../../datasource/jsr/util.ts';
|
|
import {
|
|
id as denoVersioningId,
|
|
isValid,
|
|
} from '../../versioning/deno/index.ts';
|
|
import type { PackageDependency } from '../types.ts';
|
|
import type { DenoManagerData } from './types.ts';
|
|
import { denoLandRegex, depValueRegex } from './utils.ts';
|
|
|
|
export const DenoLock = Json.pipe(
|
|
// this schema is version 5
|
|
// https://github.com/denoland/vscode_deno/blob/7e125c6ffcdcdebd587f97be5341d404f5335b87/schemas/lockfile.schema.json
|
|
z.object({
|
|
version: z.string(),
|
|
specifiers: LooseRecord(z.string()).catch({}),
|
|
redirects: LooseRecord(z.string()).catch({}),
|
|
remote: LooseRecord(z.string()).catch({}),
|
|
}),
|
|
).transform(({ version, specifiers, redirects, remote }) => {
|
|
const lockedVersions: Record<string, string> = {};
|
|
for (const [key, val] of Object.entries(specifiers)) {
|
|
// pick "7.1.3" from "7.1.3_pkgname@4.0.3_@types+pkgname@1.0.1"
|
|
const match = regEx(/^(?<lockedVersion>[^_\s]+)/).exec(val);
|
|
if (match?.groups?.lockedVersion) {
|
|
lockedVersions[key] = match.groups.lockedVersion;
|
|
}
|
|
}
|
|
|
|
const redirectVersions: Record<string, string> = {};
|
|
for (const [key, val] of Object.entries(redirects)) {
|
|
redirectVersions[key] = val;
|
|
}
|
|
|
|
const remoteVersions = new Set<string>();
|
|
for (const key of Object.keys(remote)) {
|
|
remoteVersions.add(key);
|
|
}
|
|
return {
|
|
lockedVersions,
|
|
redirectVersions,
|
|
remoteVersions,
|
|
lockfileVersion: parseInt(version, 10),
|
|
};
|
|
});
|
|
|
|
export const DenoDependency = z
|
|
.object({
|
|
depValue: z.string(),
|
|
depType: z.string(),
|
|
})
|
|
.transform(({ depValue, depType }): PackageDependency<DenoManagerData> => {
|
|
// deno datasource
|
|
// Check for https://deno.land/x/ URLs first
|
|
const denoLandMatch = denoLandRegex.exec(depValue);
|
|
if (denoLandMatch?.groups?.rawPackageName) {
|
|
return {
|
|
datasource: 'deno',
|
|
depType,
|
|
depName: joinUrlParts(
|
|
'https://deno.land',
|
|
denoLandMatch.groups.rawPackageName,
|
|
),
|
|
currentValue: denoLandMatch.groups.currentValue,
|
|
// set full URL for getLockedVersion
|
|
currentRawValue: denoLandMatch[0],
|
|
};
|
|
}
|
|
|
|
// other datasources
|
|
|
|
const match = depValueRegex.exec(depValue);
|
|
if (match?.groups?.datasource && match?.groups.depName) {
|
|
const datasource = match.groups.datasource;
|
|
const depName = match.groups.depName;
|
|
const currentValue = match.groups.currentValue;
|
|
const currentRawValue = match[0];
|
|
// npm datasource
|
|
if (datasource === 'npm') {
|
|
const dep: PackageDependency = {
|
|
datasource,
|
|
versioning: denoVersioningId,
|
|
};
|
|
|
|
if (depName && !validateNpmPackageName(depName).validForOldPackages) {
|
|
dep.skipReason = 'invalid-name';
|
|
return dep;
|
|
}
|
|
|
|
if (currentValue && !isValid(currentValue)) {
|
|
dep.skipReason = 'invalid-version';
|
|
return dep;
|
|
}
|
|
return {
|
|
...dep,
|
|
depName,
|
|
currentValue,
|
|
depType,
|
|
currentRawValue,
|
|
};
|
|
}
|
|
// jsr datasource
|
|
if (datasource === 'jsr') {
|
|
const dep: PackageDependency = {
|
|
datasource,
|
|
versioning: denoVersioningId,
|
|
};
|
|
|
|
if (depName && !extractJsrPackageName(depName)) {
|
|
dep.skipReason = 'invalid-name';
|
|
return dep;
|
|
}
|
|
|
|
if (currentValue && !isValid(currentValue)) {
|
|
dep.skipReason = 'invalid-version';
|
|
return dep;
|
|
}
|
|
return {
|
|
...dep,
|
|
depName,
|
|
currentValue,
|
|
depType,
|
|
currentRawValue,
|
|
};
|
|
}
|
|
}
|
|
|
|
return {
|
|
depType,
|
|
depName: depValue,
|
|
skipStage: 'extract',
|
|
skipReason: 'unsupported',
|
|
};
|
|
});
|
|
|
|
export const Imports = LooseRecord(z.string())
|
|
.catch({})
|
|
.transform((imports) =>
|
|
Object.values(imports).map((depValue) => ({
|
|
depValue,
|
|
depType: 'imports',
|
|
})),
|
|
)
|
|
.pipe(z.array(DenoDependency));
|
|
|
|
export const Scopes = LooseRecord(LooseRecord(z.string()))
|
|
.catch({})
|
|
.transform((scopes) =>
|
|
Object.values(scopes).flatMap((scopeDependencies) =>
|
|
Object.values(scopeDependencies).map((depValue) => ({
|
|
depValue,
|
|
depType: 'scopes',
|
|
})),
|
|
),
|
|
)
|
|
.pipe(z.array(DenoDependency));
|
|
|
|
/**
|
|
* dependency in `tasks` can't sync lock file updating due to `deno install` is not supported
|
|
*/
|
|
export const Tasks = LooseRecord(
|
|
z.union([
|
|
z.string().transform((depValue) => ({ depValue, depType: 'tasks' })),
|
|
z.object({ command: z.string() }).transform(({ command }) => ({
|
|
depValue: command,
|
|
depType: 'tasks.command',
|
|
})),
|
|
]),
|
|
)
|
|
.catch({})
|
|
.transform((tasks) =>
|
|
Object.values(tasks).map(({ depValue, depType }) => ({
|
|
depValue,
|
|
depType,
|
|
})),
|
|
)
|
|
.pipe(z.array(DenoDependency));
|
|
|
|
export const CompilerOptionsTypes = LooseArray(z.string())
|
|
.catch([])
|
|
.transform((types) =>
|
|
types.map((depValue) => ({ depValue, depType: 'compilerOptions.types' })),
|
|
)
|
|
.pipe(z.array(DenoDependency));
|
|
|
|
export const CompilerOptionsJsxImportSource = z
|
|
.union([
|
|
z
|
|
.string()
|
|
.transform((depValue) => [
|
|
{ depValue, depType: 'compilerOptions.jsxImportSource' },
|
|
]),
|
|
z.undefined().transform(() => []),
|
|
])
|
|
.pipe(z.array(DenoDependency));
|
|
|
|
export const CompilerOptionsJsxImportSourceTypes = z
|
|
.union([
|
|
z
|
|
.string()
|
|
.transform((depValue) => [
|
|
{ depValue, depType: 'compilerOptions.jsxImportSourceTypes' },
|
|
]),
|
|
z.undefined().transform(() => []),
|
|
])
|
|
.pipe(z.array(DenoDependency));
|
|
|
|
export const Lint = z
|
|
.object({
|
|
plugins: LooseArray(z.string()).catch([]),
|
|
})
|
|
.transform((lint) =>
|
|
lint.plugins.map((depValue) => ({ depValue, depType: 'lint.plugins' })),
|
|
)
|
|
.pipe(z.array(DenoDependency));
|
|
|
|
export const Lock = z.union([
|
|
z.string().transform((path) => path),
|
|
z.boolean().transform((enabled) => enabled && 'deno.lock'),
|
|
z
|
|
.object({
|
|
path: z.string().optional(),
|
|
})
|
|
.transform((obj) => obj.path),
|
|
]);
|
|
|
|
export const Workspace = z.union([
|
|
z.array(z.string()).transform((workspaces) => workspaces),
|
|
z
|
|
.object({
|
|
members: z.array(z.string()),
|
|
})
|
|
.transform((workspace) => workspace.members),
|
|
]);
|
|
|
|
const DenoPackageFile = z
|
|
.object({
|
|
lock: z.optional(Lock),
|
|
workspace: z.optional(Workspace),
|
|
importMap: z.string().optional(),
|
|
imports: z.optional(Imports).default([]),
|
|
scopes: z.optional(Scopes).default([]),
|
|
tasks: z.optional(Tasks).default([]),
|
|
compilerOptions: z
|
|
.optional(
|
|
z.object({
|
|
types: CompilerOptionsTypes,
|
|
jsxImportSource: CompilerOptionsJsxImportSource.optional().default(
|
|
[],
|
|
),
|
|
jsxImportSourceTypes:
|
|
CompilerOptionsJsxImportSourceTypes.optional().default([]),
|
|
}),
|
|
)
|
|
.default({ types: [], jsxImportSource: [], jsxImportSourceTypes: [] }),
|
|
lint: z.optional(Lint).default([]),
|
|
})
|
|
.transform(
|
|
({
|
|
lock,
|
|
imports,
|
|
scopes,
|
|
tasks,
|
|
compilerOptions,
|
|
lint,
|
|
importMap,
|
|
workspace,
|
|
}) => ({
|
|
lock,
|
|
importMap,
|
|
managerData: {
|
|
workspaces: workspace,
|
|
},
|
|
dependencies: [
|
|
...imports,
|
|
...scopes,
|
|
...tasks,
|
|
...compilerOptions.types,
|
|
...compilerOptions.jsxImportSource,
|
|
...compilerOptions.jsxImportSourceTypes,
|
|
...lint,
|
|
],
|
|
}),
|
|
);
|
|
|
|
export const DenoExtract = z.object({
|
|
content: Jsonc.pipe(DenoPackageFile),
|
|
fileName: z.string(),
|
|
});
|
|
export type DenoExtract = z.infer<typeof DenoExtract>;
|
|
|
|
export const ImportMapExtract = Json.pipe(
|
|
z.object({
|
|
imports: z.optional(Imports).default([]),
|
|
scopes: z.optional(Scopes).default([]),
|
|
}),
|
|
).transform(({ imports, scopes }) => ({
|
|
dependencies: [...imports, ...scopes],
|
|
}));
|
|
export type ImportMapExtract = z.infer<typeof ImportMapExtract>;
|
|
|
|
// All object needs passthrough to keep original field of package file and all field should be optional
|
|
export const UpdateDenoJsonFile = Jsonc.pipe(
|
|
z
|
|
.object({
|
|
imports: z.record(z.string(), z.string()).optional(),
|
|
scopes: z.record(z.string(), z.record(z.string(), z.string())).optional(),
|
|
tasks: z
|
|
.record(
|
|
z.string(),
|
|
z.union([
|
|
z.string(),
|
|
z.object({ command: z.string().optional() }).passthrough(),
|
|
]),
|
|
)
|
|
.optional(),
|
|
compilerOptions: z
|
|
.object({
|
|
types: z.array(z.string()).optional(),
|
|
jsxImportSource: z.string().optional(),
|
|
jsxImportSourceTypes: z.string().optional(),
|
|
})
|
|
.passthrough()
|
|
.optional(),
|
|
lint: z
|
|
.object({
|
|
plugins: z.array(z.string()).optional(),
|
|
})
|
|
.passthrough()
|
|
.optional(),
|
|
})
|
|
.passthrough(),
|
|
);
|
|
export type UpdateDenoJsonFile = z.infer<typeof UpdateDenoJsonFile>;
|
|
|
|
// All object needs passthrough to keep original field of package file and all field should be optional
|
|
export const UpdateImportMapJsonFile = Json.pipe(
|
|
z
|
|
.object({
|
|
imports: z.record(z.string(), z.string()).optional(),
|
|
scopes: z.record(z.string(), z.record(z.string(), z.string())).optional(),
|
|
})
|
|
.passthrough(),
|
|
);
|
|
export type UpdateImportMapJsonFile = z.infer<typeof UpdateImportMapJsonFile>;
|