Brings the working codebase (Next.js app, auth system, Stripe billing, Docker/deploy config, tests, docs) into version control on top of the placeholder initial commit, and adds account self-deletion (Danger Zone in Settings, password + typed-email confirmation, cascading DB cleanup, Stripe cancellation) per GDPR right-to-erasure. Excludes local build caches, node_modules, and internal agent scratch files; .gitignore hardened to keep those out going forward. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
99 lines
3.3 KiB
JavaScript
99 lines
3.3 KiB
JavaScript
/**
|
|
* ESM hooks for running integration tests without `tsx`.
|
|
*
|
|
* The sandbox blocks esbuild's pipe-based service-worker spawn (EPERM), so
|
|
* this loader performs the TypeScript transform in-process with the locally
|
|
* installed `typescript` package (ts.transpileModule — pure JS, no child
|
|
* process), resolves the tsconfig `@/*` path alias, probes extensions for
|
|
* extensionless relative imports, and falls back to raw package-subpath
|
|
* files (`next/headers` etc.) that lack an exports map.
|
|
*
|
|
* Usage: node --import ./tests/integration/tsconfig-paths-loader.mjs tests/integration/<suite>.test.ts
|
|
*/
|
|
import { pathToFileURL, fileURLToPath } from "node:url";
|
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
import { dirname, join, resolve as pathResolve } from "node:path";
|
|
import ts from "typescript";
|
|
|
|
const ROOT = pathResolve(process.cwd());
|
|
|
|
const EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
|
|
|
|
function isFile(p) {
|
|
try {
|
|
return statSync(p).isFile();
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/** Resolve a specifier target, probing extensions and index files. */
|
|
function probe(base) {
|
|
if (isFile(base)) return base;
|
|
for (const ext of EXTENSIONS) {
|
|
if (isFile(base + ext)) return base + ext;
|
|
}
|
|
for (const ext of EXTENSIONS) {
|
|
if (isFile(join(base, "index" + ext))) return join(base, "index" + ext);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/** Fallback for bare package subpaths (e.g. `next/headers`) that lack an exports map. */
|
|
function probePackage(spec) {
|
|
const base = pathResolve(ROOT, "node_modules", spec);
|
|
for (const ext of [".js", ".mjs", ".cjs", ".json"]) {
|
|
if (isFile(base + ext)) return base + ext;
|
|
}
|
|
if (isFile(base)) return base;
|
|
return null;
|
|
}
|
|
|
|
export async function resolve(specifier, context, nextResolve) {
|
|
if (specifier.startsWith("@/")) {
|
|
const target = probe(pathResolve(ROOT, "src", specifier.slice(2)));
|
|
if (target) return { url: pathToFileURL(target).href, shortCircuit: true };
|
|
} else if (
|
|
(specifier.startsWith("./") || specifier.startsWith("../")) &&
|
|
context.parentURL
|
|
) {
|
|
const parent = fileURLToPath(context.parentURL);
|
|
const target = probe(pathResolve(dirname(parent), specifier));
|
|
if (target) return { url: pathToFileURL(target).href, shortCircuit: true };
|
|
} else if (!specifier.startsWith("node:")) {
|
|
try {
|
|
return await nextResolve(specifier, context);
|
|
} catch (error) {
|
|
const target = probePackage(specifier);
|
|
if (target) return { url: pathToFileURL(target).href, shortCircuit: true };
|
|
throw error;
|
|
}
|
|
}
|
|
return nextResolve(specifier, context);
|
|
}
|
|
|
|
export async function load(url, context, nextLoad) {
|
|
if (url.startsWith("file:")) {
|
|
const filePath = fileURLToPath(url);
|
|
if (/\.(ts|tsx|mts|cts)$/.test(filePath)) {
|
|
const source = readFileSync(filePath, "utf8");
|
|
const out = ts.transpileModule(source, {
|
|
fileName: filePath,
|
|
reportDiagnostics: false,
|
|
compilerOptions: {
|
|
module: ts.ModuleKind.ESNext,
|
|
moduleResolution: ts.ModuleResolutionKind.Bundler,
|
|
target: ts.ScriptTarget.ES2022,
|
|
isolatedModules: true,
|
|
esModuleInterop: true,
|
|
allowJs: true,
|
|
jsx: ts.JsxEmit.ReactJSX,
|
|
verbatimModuleSyntax: false,
|
|
},
|
|
});
|
|
return { format: "module", source: out.outputText, shortCircuit: true };
|
|
}
|
|
}
|
|
return nextLoad(url, context);
|
|
}
|