chore: import upstream snapshot with attribution
Lint and Format Check / lint-and-format (push) Failing after 0s
Check Migrations / Check for duplicate migration numbers (push) Failing after 1s
CI Pre-merge Check / CI Pre-merge Check (push) Failing after 2m17s

This commit is contained in:
wehub-resource-sync
2026-07-13 12:23:40 +08:00
commit 3a28426bf4
1399 changed files with 257375 additions and 0 deletions
@@ -0,0 +1,95 @@
/**
* Detects migration files that share the same numeric prefix.
*
* node-pg-migrate orders migrations by their leading number, so two files with
* the same prefix (e.g. `047_a.sql` and `047_b.sql`) have an ambiguous order and
* can apply inconsistently across environments. This guard runs in CI (and as a
* unit test) to stop new duplicates from landing on main.
*
* A small set of historical duplicates already exists on main and is grandfathered
* in via ALLOWED_DUPLICATES — those are tolerated, anything new is rejected.
*
* Exports `findDuplicateMigrations()` for the test suite; runs as a CLI (exit 1 on
* new duplicates) when executed directly.
*/
/* global console, process */
import { readdirSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
const __dirname = dirname(fileURLToPath(import.meta.url));
export const MIGRATIONS_DIR = join(__dirname, '..', 'src', 'infra', 'database', 'migrations');
// Pre-existing duplicate prefixes on main. Do not add to this list — fix the
// duplicate instead. These remain only because the migrations already shipped.
export const ALLOWED_DUPLICATES = new Set(['033', '047']);
const MIGRATION_FILE = /^(\d+)_.*\.sql$/;
/**
* @param {string} [dir] migrations directory to scan
* @returns {{ count: number, newDuplicates: Array<{prefix:string, files:string[]}>,
* grandfathered: Array<{prefix:string, files:string[]}>, nextPrefix: string }}
*/
export function findDuplicateMigrations(dir = MIGRATIONS_DIR) {
const byPrefix = new Map();
for (const name of readdirSync(dir)) {
const match = MIGRATION_FILE.exec(name);
if (!match) continue;
const prefix = match[1];
if (!byPrefix.has(prefix)) byPrefix.set(prefix, []);
byPrefix.get(prefix).push(name);
}
const newDuplicates = [];
const grandfathered = [];
for (const [prefix, files] of byPrefix) {
if (files.length < 2) continue;
files.sort();
(ALLOWED_DUPLICATES.has(prefix) ? grandfathered : newDuplicates).push({ prefix, files });
}
const maxPrefix = byPrefix.size
? Math.max(...[...byPrefix.keys()].map((p) => parseInt(p, 10)))
: -1;
return {
count: byPrefix.size,
newDuplicates,
grandfathered,
nextPrefix: String(maxPrefix + 1).padStart(3, '0'),
};
}
function main() {
const { count, newDuplicates, grandfathered, nextPrefix } = findDuplicateMigrations();
if (grandfathered.length > 0) {
console.log('Known (grandfathered) duplicate migration prefixes:');
for (const { prefix, files } of grandfathered) {
console.log(` ${prefix}: ${files.join(', ')}`);
}
}
if (newDuplicates.length > 0) {
console.error('\nERROR: duplicate migration number(s) detected:');
for (const { prefix, files } of newDuplicates) {
console.error(` ${prefix}: ${files.join(', ')}`);
}
console.error(
`\nEach migration needs a unique number. Renumber the new file to the next ` +
`available prefix (currently ${nextPrefix}_).`
);
process.exit(1);
}
console.log(`\nOK: ${count} unique migration number(s), no new duplicates.`);
}
// Run as a CLI only when executed directly, not when imported by tests.
if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
main();
}
+223
View File
@@ -0,0 +1,223 @@
#!/bin/bash
# Test script for Deno Subhosting migration
# Usage: ./scripts/test-deno-subhosting.sh [API_URL]
set -e
API_URL="${1:-http://localhost:7130}"
PASS=0
FAIL=0
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
log_info() { echo -e "${YELLOW}[INFO]${NC} $1"; }
log_pass() { echo -e "${GREEN}[PASS]${NC} $1"; PASS=$((PASS+1)); }
log_fail() { echo -e "${RED}[FAIL]${NC} $1"; FAIL=$((FAIL+1)); }
# Poll for deployment completion, sets DEPLOY_URL and DEPLOY_STATUS
wait_for_deployment() {
local max_attempts=${1:-30}
local attempt=0
DEPLOY_URL=""
DEPLOY_STATUS=""
while [ $attempt -lt $max_attempts ]; do
LIST_RESPONSE=$(curl -s "$API_URL/api/functions")
DEPLOY_STATUS=$(echo "$LIST_RESPONSE" | grep -o '"deployment":{[^}]*}' | grep -o '"status":"[^"]*"' | cut -d'"' -f4)
DEPLOY_URL=$(echo "$LIST_RESPONSE" | grep -o '"deployment":{[^}]*}' | grep -o '"url":"[^"]*"' | cut -d'"' -f4)
if [ "$DEPLOY_STATUS" = "success" ]; then
return 0
elif [ "$DEPLOY_STATUS" = "failed" ]; then
return 1
fi
echo -n "."
sleep 2
attempt=$((attempt+1))
done
return 1
}
# -----------------------------------------------------------------------------
# Prerequisites Check
# -----------------------------------------------------------------------------
log_info "Checking prerequisites..."
# Check if API is reachable
if ! curl -s "$API_URL/api/health" > /dev/null 2>&1; then
log_fail "API not reachable at $API_URL"
echo "Make sure the backend is running: cd backend && npm run dev"
exit 1
fi
log_pass "API reachable at $API_URL"
# -----------------------------------------------------------------------------
# Test 1: Create Function
# -----------------------------------------------------------------------------
log_info "Test 1: Creating test function..."
CREATE_RESPONSE=$(curl -s -X POST "$API_URL/api/functions" \
-H "Content-Type: application/json" \
-d '{
"name": "subhosting-test",
"slug": "subhosting-test",
"code": "export default async function(req: Request) {\n const url = new URL(req.url);\n return new Response(JSON.stringify({\n message: \"Hello from Deno Subhosting!\",\n path: url.pathname,\n timestamp: new Date().toISOString()\n }), {\n headers: { \"Content-Type\": \"application/json\" }\n });\n}",
"status": "active"
}')
if echo "$CREATE_RESPONSE" | grep -q '"slug":"subhosting-test"'; then
log_pass "Function created successfully"
else
log_fail "Failed to create function: $CREATE_RESPONSE"
fi
# -----------------------------------------------------------------------------
# Test 2: Wait for Deployment
# -----------------------------------------------------------------------------
log_info "Test 2: Waiting for deployment to complete..."
if wait_for_deployment 30; then
log_pass "Deployment succeeded: $DEPLOY_URL"
else
if [ "$DEPLOY_STATUS" = "failed" ]; then
log_fail "Deployment failed"
else
log_fail "Deployment timed out"
fi
fi
# -----------------------------------------------------------------------------
# Test 3: Invoke Function on Deno Subhosting
# -----------------------------------------------------------------------------
if [ -n "$DEPLOY_URL" ] && [ "$DEPLOY_STATUS" = "success" ]; then
log_info "Test 3: Invoking function on Deno Subhosting..."
INVOKE_RESPONSE=$(curl -s "$DEPLOY_URL/subhosting-test" 2>/dev/null || echo "CURL_FAILED")
if echo "$INVOKE_RESPONSE" | grep -q '"message":"Hello from Deno Subhosting!"'; then
log_pass "Function invoked successfully on Deno Subhosting"
echo "Response: $INVOKE_RESPONSE"
else
log_fail "Function invocation failed: $INVOKE_RESPONSE"
fi
else
log_info "Skipping invocation test (deployment not successful)"
fi
# -----------------------------------------------------------------------------
# Test 4: Update Function
# -----------------------------------------------------------------------------
log_info "Test 4: Updating function..."
UPDATE_RESPONSE=$(curl -s -X PATCH "$API_URL/api/functions/subhosting-test" \
-H "Content-Type: application/json" \
-d '{
"code": "export default async function(req: Request) {\n return new Response(JSON.stringify({\n message: \"Updated function!\",\n version: 2\n }), {\n headers: { \"Content-Type\": \"application/json\" }\n });\n}"
}')
if echo "$UPDATE_RESPONSE" | grep -q '"slug":"subhosting-test"'; then
log_pass "Function updated successfully"
else
log_fail "Failed to update function: $UPDATE_RESPONSE"
fi
# Wait for redeployment and get new deployment URL
log_info "Waiting for redeployment..."
if wait_for_deployment 30; then
UPDATED_RESPONSE=$(curl -s "$DEPLOY_URL/subhosting-test" 2>/dev/null || echo "CURL_FAILED")
if echo "$UPDATED_RESPONSE" | grep -q '"version":2'; then
log_pass "Updated function works on Deno Subhosting"
else
log_fail "Updated function not reflecting changes: $UPDATED_RESPONSE"
fi
else
log_fail "Redeployment failed or timed out"
fi
# -----------------------------------------------------------------------------
# Test 5: Create Function with Secret Access
# -----------------------------------------------------------------------------
log_info "Test 5: Creating function that accesses secrets..."
SECRET_FUNC_RESPONSE=$(curl -s -X POST "$API_URL/api/functions" \
-H "Content-Type: application/json" \
-d '{
"name": "secret-test",
"slug": "secret-test",
"code": "export default async function(req: Request) {\n const envKeys = Object.keys(Deno.env.toObject()).filter(k => !k.startsWith(\"DENO_\"));\n return new Response(JSON.stringify({\n message: \"Secret test\",\n availableEnvVars: envKeys.length,\n hasSecrets: envKeys.length > 0\n }), {\n headers: { \"Content-Type\": \"application/json\" }\n });\n}",
"status": "active"
}')
if echo "$SECRET_FUNC_RESPONSE" | grep -q '"slug":"secret-test"'; then
log_pass "Secret test function created"
else
log_fail "Failed to create secret test function: $SECRET_FUNC_RESPONSE"
fi
# Wait for deployment
sleep 10
if [ -n "$DEPLOY_URL" ]; then
SECRET_RESPONSE=$(curl -s "$DEPLOY_URL/secret-test" 2>/dev/null || echo "CURL_FAILED")
log_info "Secret test response: $SECRET_RESPONSE"
fi
# -----------------------------------------------------------------------------
# Test 6: List Functions with Deployment Info
# -----------------------------------------------------------------------------
log_info "Test 6: Checking list functions includes deployment info..."
LIST_FINAL=$(curl -s "$API_URL/api/functions")
if echo "$LIST_FINAL" | grep -q '"deployment"'; then
log_pass "List functions includes deployment info"
echo "$LIST_FINAL" | jq '.deployment' 2>/dev/null || echo "Deployment info present"
else
log_fail "List functions missing deployment info"
fi
# -----------------------------------------------------------------------------
# Test 7: Delete Function
# -----------------------------------------------------------------------------
log_info "Test 7: Deleting test functions..."
curl -s -X DELETE "$API_URL/api/functions/subhosting-test" > /dev/null
curl -s -X DELETE "$API_URL/api/functions/secret-test" > /dev/null
log_pass "Functions deleted (cleanup)"
# Wait for redeployment without deleted functions
sleep 5
# Verify functions are gone from Deno Subhosting
if [ -n "$DEPLOY_URL" ]; then
GONE_RESPONSE=$(curl -s "$DEPLOY_URL/subhosting-test" 2>/dev/null || echo "CURL_FAILED")
if echo "$GONE_RESPONSE" | grep -q '"error":"Function not found"'; then
log_pass "Deleted function no longer accessible on Deno Subhosting"
else
log_info "Function may still be cached: $GONE_RESPONSE"
fi
fi
# -----------------------------------------------------------------------------
# Summary
# -----------------------------------------------------------------------------
echo ""
echo "============================================"
echo "Test Summary"
echo "============================================"
echo -e "${GREEN}Passed: $PASS${NC}"
echo -e "${RED}Failed: $FAIL${NC}"
echo "============================================"
if [ $FAIL -gt 0 ]; then
exit 1
fi