-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathvalidators.ts
More file actions
171 lines (145 loc) · 5.49 KB
/
Copy pathvalidators.ts
File metadata and controls
171 lines (145 loc) · 5.49 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
import * as core from '@actions/core';
import * as github from '@actions/github';
import * as fs from 'fs';
import * as path from 'path';
export interface ActionInputs {
apiKey: string;
sourceType: SourceType;
agentName?: string;
agentVersion: string;
gitRepository?: string;
gitRef?: string;
path?: string;
npmPackage?: string;
npmRegistryUrl?: string;
pipPackage?: string;
pipIndexUrl?: string;
setupCommands?: string[];
isPublic?: boolean;
apiUrl: string;
objectTtlDays?: number;
}
export type SourceType = 'git' | 'tar' | 'file' | 'npm' | 'pip';
export function getInputs(): ActionInputs {
// Get all inputs
const sourceType = core.getInput('source-type', { required: true }) as SourceType;
const setupCommandsRaw = core.getInput('setup-commands');
const objectTtlDaysRaw = core.getInput('object-ttl-days');
const inputs: ActionInputs = {
apiKey: core.getInput('api-key', { required: true }),
sourceType,
agentName: core.getInput('agent-name') || undefined,
agentVersion: core.getInput('agent-version', { required: true }),
gitRepository: core.getInput('git-repository') || undefined,
gitRef: core.getInput('git-ref') || undefined,
path: core.getInput('path') || undefined,
npmPackage: core.getInput('npm-package') || undefined,
npmRegistryUrl: core.getInput('npm-registry-url') || undefined,
pipPackage: core.getInput('pip-package') || undefined,
pipIndexUrl: core.getInput('pip-index-url') || undefined,
setupCommands: setupCommandsRaw
? setupCommandsRaw
.split('\n')
.map(cmd => cmd.trim())
.filter(cmd => cmd.length > 0)
: undefined,
apiUrl: core.getInput('api-url') || 'https://api.runloop.ai',
objectTtlDays: objectTtlDaysRaw ? parseInt(objectTtlDaysRaw, 10) : undefined,
};
// Hidden: if called from runloopai/runloop, set is_public based on "public:" version prefix
const { owner, repo } = github.context.repo;
if (owner === 'runloopai' && repo === 'runloop') {
if (inputs.agentVersion.startsWith('public:')) {
inputs.agentVersion = inputs.agentVersion.slice('public:'.length);
inputs.isPublic = true;
}
}
return inputs;
}
export function validateInputs(inputs: ActionInputs): void {
// Validate source type
const validSourceTypes: SourceType[] = ['git', 'tar', 'file', 'npm', 'pip'];
if (!validSourceTypes.includes(inputs.sourceType)) {
throw new Error(
`Invalid source-type: ${inputs.sourceType}. Must be one of: ${validSourceTypes.join(', ')}`
);
}
// Validate source-specific inputs
switch (inputs.sourceType) {
case 'tar':
case 'file':
if (!inputs.path) {
throw new Error(`path is required when source-type is "${inputs.sourceType}"`);
}
validatePath(inputs.path, inputs.sourceType);
break;
case 'git':
// Git source doesn't require explicit repository (uses current repo by default)
// Validation happens in git-utils.ts
break;
case 'npm':
if (!inputs.npmPackage) {
throw new Error('npm-package is required when source-type is "npm"');
}
break;
case 'pip':
if (!inputs.pipPackage) {
throw new Error('pip-package is required when source-type is "pip"');
}
break;
default: {
// Exhaustiveness check - this should never happen
const exhaustiveCheck: never = inputs.sourceType;
throw new Error(`Unsupported source-type: ${exhaustiveCheck as string}`);
}
}
// Validate API key format (should not be empty)
if (!inputs.apiKey || inputs.apiKey.trim().length === 0) {
throw new Error('api-key cannot be empty');
}
// Validate agentVersion format (semver or SHA)
validateAgentVersion(inputs.agentVersion);
// Validate objectTtlDays if provided
if (inputs.objectTtlDays !== undefined) {
if (isNaN(inputs.objectTtlDays) || inputs.objectTtlDays <= 0) {
throw new Error('object-ttl-days must be a positive number');
}
}
}
function validatePath(inputPath: string, sourceType: SourceType): void {
if (sourceType !== 'file' && sourceType !== 'tar') {
throw new Error(`validatePath is undefined when source-type is "${sourceType}": ${inputPath}`);
}
const absolutePath = resolvePath(inputPath);
// Check if path exists
if (!fs.existsSync(absolutePath)) {
throw new Error(`Path does not exist: ${inputPath} (resolved to: ${absolutePath})`);
}
// Validate based on source type
const stats = fs.statSync(absolutePath);
if (!stats.isFile()) {
throw new Error(`Path must be a file when source-type is "${sourceType}": ${inputPath}`);
}
}
export function resolvePath(inputPath: string): string {
const workspace = process.env.GITHUB_WORKSPACE;
if (!workspace) {
throw new Error('GITHUB_WORKSPACE environment variable is not set');
}
return path.isAbsolute(inputPath) ? inputPath : path.join(workspace, inputPath);
}
// Semver pattern: major.minor.patch with optional pre-release and build metadata
const SEMVER_REGEX = /^\d+\.\d+\.\d+(-[\w.-]+)?(\+[\w.-]+)?$/;
// Git SHA pattern: 7-40 hex characters (short or full SHA)
const SHA_REGEX = /^[a-f0-9]{7,40}$/i;
function validateAgentVersion(version: string): void {
if (!version || version.trim().length === 0) {
throw new Error('agent-version cannot be empty');
}
const trimmed = version.trim();
if (!SEMVER_REGEX.test(trimmed) && !SHA_REGEX.test(trimmed)) {
throw new Error(
`Invalid agent-version: "${version}". Must be a semver string (e.g., "2.0.65") or a git SHA (7-40 hex characters).`
);
}
}