@somewhere-tech/cli 0.10.0 → 0.12.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/README.md +3 -0
- package/dist/commands/auth.js +15 -7
- package/dist/commands/auth.js.map +1 -1
- package/dist/commands/deploy.js +25 -5
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/dev.js +70 -4
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/exec.js +109 -0
- package/dist/commands/exec.js.map +1 -0
- package/dist/commands/mcp.js +266 -63
- package/dist/commands/mcp.js.map +1 -1
- package/dist/commands/pull.js +8 -6
- package/dist/commands/pull.js.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/build-errors.js +147 -0
- package/dist/lib/build-errors.js.map +1 -0
- package/dist/lib/client.js +48 -4
- package/dist/lib/client.js.map +1 -1
- package/dist/lib/config.js +34 -0
- package/dist/lib/config.js.map +1 -1
- package/dist/local/envfile.js +40 -0
- package/dist/local/envfile.js.map +1 -0
- package/dist/local/loader.js +103 -0
- package/dist/local/loader.js.map +1 -0
- package/dist/local/router.js +114 -0
- package/dist/local/router.js.map +1 -0
- package/dist/local/runtime.js +268 -0
- package/dist/local/runtime.js.map +1 -0
- package/dist/local/server.js +169 -0
- package/dist/local/server.js.map +1 -0
- package/package.json +8 -4
- package/runtime/platform-context.mjs +3678 -0
- package/runtime/sw-init.mjs +237 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
// VENDORED from worker/src/utils/function-bundle.ts (SW_INIT_JS) @ 58927b3
|
|
2
|
+
// — the exact runtime deployed functions run against. Do not edit by hand;
|
|
3
|
+
// re-sync with: node scripts/extract-runtime.mjs <monorepo>
|
|
4
|
+
// Generated by somewhere.tech deploy pipeline. Sets up the global
|
|
5
|
+
// surfaces user modules need at module-load time.
|
|
6
|
+
globalThis.sw = globalThis.sw || {};
|
|
7
|
+
|
|
8
|
+
/** Parse "10/minute" / "60/hour" / "1000/day" into { max, windowSeconds }. */
|
|
9
|
+
function __sw_parseRateLimit(spec) {
|
|
10
|
+
if (typeof spec !== 'string') return null;
|
|
11
|
+
var m = spec.match(/^\s*(\d+)\s*\/\s*(second|minute|hour|day)s?\s*$/i);
|
|
12
|
+
if (!m) return null;
|
|
13
|
+
var window = { second: 1, minute: 60, hour: 3600, day: 86400 }[m[2].toLowerCase()];
|
|
14
|
+
return { max: Number(m[1]), windowSeconds: window };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Best-effort client IP for rate-limit keying when no user is authed. */
|
|
18
|
+
function __sw_clientIp(request) {
|
|
19
|
+
var h = request.headers;
|
|
20
|
+
return (
|
|
21
|
+
h.get('cf-connecting-ip') ||
|
|
22
|
+
h.get('x-forwarded-for')?.split(',')[0]?.trim() ||
|
|
23
|
+
h.get('x-real-ip') ||
|
|
24
|
+
'anon'
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Validate `value` against a small declarative schema. Returns an
|
|
30
|
+
* array of error messages (empty if valid). Supports leaf strings as
|
|
31
|
+
* type names ('string', 'email', 'number', 'boolean', 'array',
|
|
32
|
+
* 'object'), trailing '?' for optional, and nested objects. Designed
|
|
33
|
+
* for body validation, not arbitrary JSON Schema.
|
|
34
|
+
*/
|
|
35
|
+
function __sw_validate(value, schema, path) {
|
|
36
|
+
path = path || '';
|
|
37
|
+
var errs = [];
|
|
38
|
+
if (typeof schema === 'string') {
|
|
39
|
+
var optional = schema.endsWith('?');
|
|
40
|
+
var type = optional ? schema.slice(0, -1) : schema;
|
|
41
|
+
if (value === undefined || value === null) {
|
|
42
|
+
if (!optional) errs.push(path + ': required');
|
|
43
|
+
return errs;
|
|
44
|
+
}
|
|
45
|
+
if (type === 'string' && typeof value !== 'string') errs.push(path + ': must be a string');
|
|
46
|
+
else if (type === 'email') {
|
|
47
|
+
if (typeof value !== 'string' || !/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(value)) errs.push(path + ': must be a valid email');
|
|
48
|
+
}
|
|
49
|
+
else if (type === 'number' && typeof value !== 'number') errs.push(path + ': must be a number');
|
|
50
|
+
else if (type === 'boolean' && typeof value !== 'boolean') errs.push(path + ': must be a boolean');
|
|
51
|
+
else if (type === 'array' && !Array.isArray(value)) errs.push(path + ': must be an array');
|
|
52
|
+
else if (type === 'object' && (typeof value !== 'object' || value === null || Array.isArray(value))) errs.push(path + ': must be an object');
|
|
53
|
+
return errs;
|
|
54
|
+
}
|
|
55
|
+
if (schema && typeof schema === 'object') {
|
|
56
|
+
if (value === null || value === undefined || typeof value !== 'object' || Array.isArray(value)) {
|
|
57
|
+
errs.push((path || 'body') + ': must be an object');
|
|
58
|
+
return errs;
|
|
59
|
+
}
|
|
60
|
+
for (var key of Object.keys(schema)) {
|
|
61
|
+
var sub = path ? path + '.' + key : key;
|
|
62
|
+
errs = errs.concat(__sw_validate(value[key], schema[key], sub));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return errs;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Build CORS headers off the config. 'same-origin' returns no
|
|
69
|
+
* Access-Control-* — that's the default browsers enforce when no
|
|
70
|
+
* header is present, exactly what we want. */
|
|
71
|
+
function __sw_corsHeaders(corsCfg, request) {
|
|
72
|
+
if (!corsCfg || corsCfg === 'same-origin') return {};
|
|
73
|
+
var origin = request.headers.get('origin') || '';
|
|
74
|
+
var allowed;
|
|
75
|
+
if (corsCfg === '*') allowed = '*';
|
|
76
|
+
else if (Array.isArray(corsCfg)) {
|
|
77
|
+
var hit = corsCfg.find(function (o) {
|
|
78
|
+
return o === origin || origin.endsWith('.' + o);
|
|
79
|
+
});
|
|
80
|
+
if (!hit) return {};
|
|
81
|
+
allowed = origin;
|
|
82
|
+
} else {
|
|
83
|
+
return {};
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
'Access-Control-Allow-Origin': allowed,
|
|
87
|
+
'Access-Control-Allow-Methods': 'GET, POST, PUT, PATCH, DELETE, OPTIONS',
|
|
88
|
+
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
|
89
|
+
'Vary': 'Origin',
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function __sw_jsonError(status, code, message, extras) {
|
|
94
|
+
var body = Object.assign({ ok: false, error: code, message: message }, extras || {});
|
|
95
|
+
return new Response(JSON.stringify(body), {
|
|
96
|
+
status: status,
|
|
97
|
+
headers: { 'Content-Type': 'application/json' },
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function __sw_mergeHeaders(response, extra) {
|
|
102
|
+
if (!extra || Object.keys(extra).length === 0) return response;
|
|
103
|
+
var headers = new Headers(response.headers);
|
|
104
|
+
for (var k of Object.keys(extra)) headers.set(k, extra[k]);
|
|
105
|
+
return new Response(response.body, {
|
|
106
|
+
status: response.status,
|
|
107
|
+
statusText: response.statusText,
|
|
108
|
+
headers: headers,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* sw.endpoint({ auth, body, rateLimit, cors, handler }) → handler.
|
|
114
|
+
*
|
|
115
|
+
* Returns an async (request, sw) => Response | object that the shim
|
|
116
|
+
* dispatches to like any other default-export handler. Pipeline:
|
|
117
|
+
*
|
|
118
|
+
* 1. CORS preflight — answer OPTIONS with 204 + allow headers.
|
|
119
|
+
* 2. Auth — required throws 401, optional enriches user when present.
|
|
120
|
+
* 3. Body validation — parses JSON, validates against schema.
|
|
121
|
+
* 4. Rate limit — per-(user|ip)-per-path counter via sw.rateLimit.
|
|
122
|
+
* 5. Handler — { body, user, headers, params, request } + sw.
|
|
123
|
+
* 6. Error wrap — clean JSON { ok:false, error, message }.
|
|
124
|
+
* 7. CORS — append Access-Control-* to the response.
|
|
125
|
+
*/
|
|
126
|
+
globalThis.sw.endpoint = function (config) {
|
|
127
|
+
config = config || {};
|
|
128
|
+
if (typeof config.handler !== 'function') {
|
|
129
|
+
throw new Error('sw.endpoint: handler is required (an async function).');
|
|
130
|
+
}
|
|
131
|
+
var auth = config.auth || 'none';
|
|
132
|
+
var bodySchema = config.body || null;
|
|
133
|
+
var rateLimit = __sw_parseRateLimit(config.rateLimit);
|
|
134
|
+
var cors = config.cors || 'same-origin';
|
|
135
|
+
|
|
136
|
+
return async function endpointHandler(request, sw) {
|
|
137
|
+
var corsHeaders = __sw_corsHeaders(cors, request);
|
|
138
|
+
if (request.method === 'OPTIONS') {
|
|
139
|
+
return new Response(null, { status: 204, headers: corsHeaders });
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Auth.
|
|
143
|
+
var user = null;
|
|
144
|
+
if (auth === 'required') {
|
|
145
|
+
try {
|
|
146
|
+
user = await sw.auth.requireUser(request);
|
|
147
|
+
} catch (e) {
|
|
148
|
+
return __sw_mergeHeaders(
|
|
149
|
+
__sw_jsonError(e.status || 401, e.code || 'AUTH_REQUIRED', e.message || 'Sign in required.'),
|
|
150
|
+
corsHeaders
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
} else if (auth === 'optional') {
|
|
154
|
+
try { user = await sw.auth.fromRequest(request); } catch (_) { /* swallow */ }
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Body validation (skipped on GET/DELETE/HEAD — no body expected).
|
|
158
|
+
var body = null;
|
|
159
|
+
if (bodySchema && request.method !== 'GET' && request.method !== 'DELETE' && request.method !== 'HEAD') {
|
|
160
|
+
try { body = await request.json(); }
|
|
161
|
+
catch (_) {
|
|
162
|
+
return __sw_mergeHeaders(
|
|
163
|
+
__sw_jsonError(400, 'INVALID_JSON', 'Request body must be valid JSON.'),
|
|
164
|
+
corsHeaders
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
var errors = __sw_validate(body, bodySchema, '');
|
|
168
|
+
if (errors.length) {
|
|
169
|
+
return __sw_mergeHeaders(
|
|
170
|
+
__sw_jsonError(400, 'VALIDATION_ERROR', errors.join('; '), { fields: errors }),
|
|
171
|
+
corsHeaders
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Rate limit. Key by user id when authed (sticky across IPs);
|
|
177
|
+
// by client IP otherwise. Path is included so two endpoints don't
|
|
178
|
+
// share a bucket.
|
|
179
|
+
if (rateLimit) {
|
|
180
|
+
var pathname = '';
|
|
181
|
+
try { pathname = new URL(request.url).pathname; } catch (_) {}
|
|
182
|
+
var rlKey = 'endpoint:' + (user?.id || __sw_clientIp(request)) + ':' + pathname;
|
|
183
|
+
try {
|
|
184
|
+
var r = await sw.rateLimit.check(rlKey, rateLimit.max, rateLimit.windowSeconds);
|
|
185
|
+
if (!r || r.allowed === false) {
|
|
186
|
+
var retry = (r && r.reset) || rateLimit.windowSeconds;
|
|
187
|
+
return __sw_mergeHeaders(
|
|
188
|
+
new Response(
|
|
189
|
+
JSON.stringify({ ok: false, error: 'RATE_LIMITED', message: 'Too many requests.', retry_after: retry }),
|
|
190
|
+
{ status: 429, headers: { 'Content-Type': 'application/json', 'Retry-After': String(retry) } }
|
|
191
|
+
),
|
|
192
|
+
corsHeaders
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
} catch (_) {
|
|
196
|
+
// fail-loudly + fail-CLOSED (tsk_9f22): if the rate-limit check
|
|
197
|
+
// itself errors we used to let the request through — silently
|
|
198
|
+
// disabling the limiter. Block instead. A rate-limited endpoint
|
|
199
|
+
// must never serve unthrottled just because the limiter hiccuped.
|
|
200
|
+
return __sw_mergeHeaders(
|
|
201
|
+
new Response(
|
|
202
|
+
JSON.stringify({ ok: false, error: 'RATE_LIMIT_UNAVAILABLE', message: 'Rate limiting is temporarily unavailable; this request was blocked. Retry shortly.' }),
|
|
203
|
+
{ status: 503, headers: { 'Content-Type': 'application/json', 'Retry-After': String(rateLimit.windowSeconds) } }
|
|
204
|
+
),
|
|
205
|
+
corsHeaders
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Handler. Catches any throw, surfacing { status, code } when
|
|
211
|
+
// present so handler code can throw rich errors without manual
|
|
212
|
+
// Response building.
|
|
213
|
+
try {
|
|
214
|
+
var result = await config.handler(
|
|
215
|
+
{ body: body, user: user, headers: request.headers, params: request.params || {}, request: request },
|
|
216
|
+
sw
|
|
217
|
+
);
|
|
218
|
+
var response;
|
|
219
|
+
if (result instanceof Response) {
|
|
220
|
+
response = result;
|
|
221
|
+
} else if (result === undefined || result === null) {
|
|
222
|
+
response = new Response(null, { status: 204 });
|
|
223
|
+
} else {
|
|
224
|
+
response = new Response(JSON.stringify(result), {
|
|
225
|
+
status: 200,
|
|
226
|
+
headers: { 'Content-Type': 'application/json' },
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
return __sw_mergeHeaders(response, corsHeaders);
|
|
230
|
+
} catch (e) {
|
|
231
|
+
return __sw_mergeHeaders(
|
|
232
|
+
__sw_jsonError(e.status || 500, e.code || 'HANDLER_ERROR', e.message || String(e)),
|
|
233
|
+
corsHeaders
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
};
|