@cortexkit/common-auth 0.1.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.
Files changed (53) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +10 -0
  3. package/dist/fs/atomic-write.d.ts +5 -0
  4. package/dist/fs/atomic-write.js +22 -0
  5. package/dist/fs/index.d.ts +5 -0
  6. package/dist/fs/index.js +3 -0
  7. package/dist/fs/lock-constants.d.ts +14 -0
  8. package/dist/fs/lock-constants.js +14 -0
  9. package/dist/fs/refresh-file-lock.d.ts +18 -0
  10. package/dist/fs/refresh-file-lock.js +392 -0
  11. package/dist/fs/with-lock.d.ts +32 -0
  12. package/dist/fs/with-lock.js +48 -0
  13. package/dist/logger/capture-sink.d.ts +14 -0
  14. package/dist/logger/capture-sink.js +13 -0
  15. package/dist/logger/engine.d.ts +44 -0
  16. package/dist/logger/engine.js +199 -0
  17. package/dist/logger/index.d.ts +6 -0
  18. package/dist/logger/index.js +3 -0
  19. package/dist/logger/redact.d.ts +11 -0
  20. package/dist/logger/redact.js +45 -0
  21. package/dist/rpc/index.d.ts +10 -0
  22. package/dist/rpc/index.js +5 -0
  23. package/dist/rpc/notifications.d.ts +29 -0
  24. package/dist/rpc/notifications.js +57 -0
  25. package/dist/rpc/port-file.d.ts +20 -0
  26. package/dist/rpc/port-file.js +164 -0
  27. package/dist/rpc/rpc-client.d.ts +8 -0
  28. package/dist/rpc/rpc-client.js +51 -0
  29. package/dist/rpc/rpc-server.d.ts +19 -0
  30. package/dist/rpc/rpc-server.js +135 -0
  31. package/dist/rpc/server-registry.d.ts +11 -0
  32. package/dist/rpc/server-registry.js +47 -0
  33. package/dist/sidebar-file/index.d.ts +2 -0
  34. package/dist/sidebar-file/index.js +1 -0
  35. package/dist/sidebar-file/sidebar-file.d.ts +23 -0
  36. package/dist/sidebar-file/sidebar-file.js +84 -0
  37. package/dist/tui/index.d.ts +8 -0
  38. package/dist/tui/index.js +21 -0
  39. package/dist/tui-build/build-tui.d.ts +23 -0
  40. package/dist/tui-build/build-tui.js +190 -0
  41. package/dist/tui-build/index.d.ts +3 -0
  42. package/dist/tui-build/index.js +2 -0
  43. package/dist/tui-build/publish-list.d.ts +2 -0
  44. package/dist/tui-build/publish-list.js +20 -0
  45. package/dist/tui-build/walker.d.ts +8 -0
  46. package/dist/tui-build/walker.js +171 -0
  47. package/dist/tui-prefs/index.d.ts +2 -0
  48. package/dist/tui-prefs/index.js +2 -0
  49. package/dist/tui-prefs/tui-preferences.d.ts +25 -0
  50. package/dist/tui-prefs/tui-preferences.js +77 -0
  51. package/dist/tui-prefs/watcher.d.ts +6 -0
  52. package/dist/tui-prefs/watcher.js +74 -0
  53. package/package.json +89 -0
@@ -0,0 +1,135 @@
1
+ import { randomBytes, timingSafeEqual } from 'node:crypto';
2
+ import { readFile, unlink } from 'node:fs/promises';
3
+ import { createServer, } from 'node:http';
4
+ import { join } from 'node:path';
5
+ import { sweepRpcState, writePortFile } from './port-file.js';
6
+ function readBody(req) {
7
+ return new Promise((resolve, reject) => {
8
+ const chunks = [];
9
+ let size = 0;
10
+ req.on('data', (chunk) => {
11
+ size += chunk.length;
12
+ if (size > 1_000_000) {
13
+ req.destroy();
14
+ reject(new Error('body too large'));
15
+ return;
16
+ }
17
+ chunks.push(chunk);
18
+ });
19
+ req.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
20
+ req.on('error', reject);
21
+ });
22
+ }
23
+ function tokenOk(header, token) {
24
+ if (!header?.startsWith('Bearer '))
25
+ return false;
26
+ const got = Buffer.from(header.slice(7));
27
+ const want = Buffer.from(token);
28
+ return got.length === want.length && timingSafeEqual(got, want);
29
+ }
30
+ export async function startRpcServer(options) {
31
+ const log = options.log ?? { warn() { }, debug() { } };
32
+ const token = randomBytes(32).toString('hex');
33
+ // The receipt timeout limits request delivery, not handler execution.
34
+ // The socket inactivity timeout must also allow slow handlers to finish.
35
+ const handlerTimeoutMs = options.timeoutMs ?? 90_000;
36
+ const receiptTimeoutMs = options.receiptTimeoutMs ?? 2_000;
37
+ let warnedMissingNotificationSession = false;
38
+ const server = createServer((req, res) => {
39
+ req.setTimeout(handlerTimeoutMs, () => {
40
+ req.socket.destroy();
41
+ });
42
+ void dispatch(req, res);
43
+ });
44
+ server.requestTimeout = receiptTimeoutMs;
45
+ server.headersTimeout = receiptTimeoutMs;
46
+ async function dispatch(req, res) {
47
+ const json = (status, value) => {
48
+ // Guard against writing to a socket that was destroyed (e.g. when
49
+ // readBody rejected after req.destroy() on an oversized body).
50
+ if (res.headersSent || res.writableEnded || res.destroyed)
51
+ return;
52
+ res.writeHead(status, { 'content-type': 'application/json' });
53
+ res.end(JSON.stringify(value));
54
+ };
55
+ try {
56
+ const url = req.url ?? '';
57
+ if (req.method === 'GET' && url === '/health')
58
+ return json(200, { ok: true });
59
+ if (req.method !== 'POST' || !url.startsWith('/rpc/'))
60
+ return json(404, { error: 'not found' });
61
+ if (!tokenOk(req.headers.authorization, token))
62
+ return json(401, { error: 'unauthorized' });
63
+ const method = url.slice('/rpc/'.length);
64
+ const body = await readBody(req);
65
+ const params = JSON.parse(body || '{}');
66
+ if (method === 'pending-notifications') {
67
+ const sessionId = typeof params.sessionId === 'string' ? params.sessionId : undefined;
68
+ if (sessionId === undefined && !warnedMissingNotificationSession) {
69
+ warnedMissingNotificationSession = true;
70
+ log.warn('rpc notification drain missing session id', {
71
+ pid: process.pid,
72
+ });
73
+ }
74
+ const messages = options.drain(Number(params.lastReceivedId ?? 0), sessionId);
75
+ return json(200, { messages });
76
+ }
77
+ if (method === 'apply') {
78
+ const result = await options.apply(params);
79
+ return json(200, result);
80
+ }
81
+ return json(404, { error: 'unknown method' });
82
+ }
83
+ catch (error) {
84
+ json(500, {
85
+ error: error instanceof Error ? error.message : String(error),
86
+ });
87
+ }
88
+ }
89
+ const port = await new Promise((resolve, reject) => {
90
+ server.on('error', reject);
91
+ server.listen(0, '127.0.0.1', () => {
92
+ const addr = server.address();
93
+ if (addr && typeof addr === 'object')
94
+ resolve(addr.port);
95
+ else
96
+ reject(new Error('no port'));
97
+ });
98
+ });
99
+ server.unref();
100
+ if (options.sweepRoot) {
101
+ try {
102
+ await sweepRpcState(options.sweepRoot, options.dir, options.isManagedDir, log);
103
+ }
104
+ catch (error) {
105
+ log.warn('rpc state sweep failed', {
106
+ pid: process.pid,
107
+ error: error instanceof Error ? error.message : String(error),
108
+ });
109
+ }
110
+ }
111
+ try {
112
+ await writePortFile(options.dir, { port, token, pid: process.pid }, { secureDir: options.secureDir });
113
+ log.debug('rpc server pid', {
114
+ pid: process.pid,
115
+ rpcPort: port,
116
+ });
117
+ }
118
+ catch (error) {
119
+ await new Promise((resolve) => server.close(() => resolve()));
120
+ throw error;
121
+ }
122
+ return {
123
+ port,
124
+ token,
125
+ async stop() {
126
+ await new Promise((resolve) => server.close(() => resolve()));
127
+ const portFile = join(options.dir, `port-${process.pid}.json`);
128
+ const current = await readFile(portFile, 'utf8')
129
+ .then((raw) => JSON.parse(raw))
130
+ .catch(() => undefined);
131
+ if (current?.port === port && current.token === token)
132
+ await unlink(portFile).catch(() => { });
133
+ },
134
+ };
135
+ }
@@ -0,0 +1,11 @@
1
+ import type { RpcServerHandle } from './rpc-server.js';
2
+ export interface RpcServerAdoption {
3
+ server: RpcServerHandle;
4
+ release: () => Promise<void>;
5
+ }
6
+ /**
7
+ * Serializes same-directory replacement. Release checks the adopted server
8
+ * identity so an older holder cannot stop or remove its successor.
9
+ * Distinct project directories remain independent.
10
+ */
11
+ export declare function adoptRpcServer(registryKey: string, rpcDir: string, create: () => Promise<RpcServerHandle>): Promise<RpcServerAdoption>;
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Serializes same-directory replacement. Release checks the adopted server
3
+ * identity so an older holder cannot stop or remove its successor.
4
+ * Distinct project directories remain independent.
5
+ */
6
+ export async function adoptRpcServer(registryKey, rpcDir, create) {
7
+ const globals = globalThis;
8
+ const key = Symbol.for(registryKey);
9
+ const registry = globals[key] ?? { servers: new Map(), pending: new Map() };
10
+ globals[key] = registry;
11
+ const { servers, pending } = registry;
12
+ const predecessor = pending.get(rpcDir) ?? Promise.resolve();
13
+ let server;
14
+ const start = predecessor
15
+ .catch(() => { })
16
+ .then(async () => {
17
+ const previous = servers.get(rpcDir);
18
+ if (previous) {
19
+ await previous.stop();
20
+ if (servers.get(rpcDir) === previous)
21
+ servers.delete(rpcDir);
22
+ }
23
+ server = await create();
24
+ servers.set(rpcDir, server);
25
+ });
26
+ pending.set(rpcDir, start);
27
+ try {
28
+ await start;
29
+ }
30
+ finally {
31
+ if (pending.get(rpcDir) === start)
32
+ pending.delete(rpcDir);
33
+ }
34
+ const adoptedServer = server;
35
+ if (!adoptedServer)
36
+ throw new Error('RPC server failed to start');
37
+ return {
38
+ server: adoptedServer,
39
+ release: async () => {
40
+ if (servers.get(rpcDir) !== adoptedServer)
41
+ return;
42
+ await adoptedServer.stop();
43
+ if (servers.get(rpcDir) === adoptedServer)
44
+ servers.delete(rpcDir);
45
+ },
46
+ };
47
+ }
@@ -0,0 +1,2 @@
1
+ export type { SidebarFile, SidebarFileHooks, SidebarFileOptions, } from './sidebar-file.js';
2
+ export { createSidebarFile } from './sidebar-file.js';
@@ -0,0 +1 @@
1
+ export { createSidebarFile } from './sidebar-file.js';
@@ -0,0 +1,23 @@
1
+ export interface SidebarFileHooks {
2
+ /** Runs on the first merge attempt, before checking for older, unlocked writers. */
3
+ beforeRecheck?: () => void | Promise<void>;
4
+ /** @internal Test seam after staging, before the ownership fence. */
5
+ beforeCommit?: () => Promise<void>;
6
+ }
7
+ export interface SidebarFileOptions<T> {
8
+ path: string;
9
+ defaultValue: T;
10
+ normalize: (parsed: unknown) => T;
11
+ timeoutMs?: number;
12
+ logger?: {
13
+ warn: (message: string, payload?: unknown) => void;
14
+ debug: (message: string, payload?: unknown) => void;
15
+ };
16
+ }
17
+ export interface SidebarFile<T> {
18
+ read(): Promise<T>;
19
+ write(value: T, hooks?: SidebarFileHooks): Promise<void>;
20
+ update(merge: (latest: T) => T | undefined, hooks?: SidebarFileHooks): Promise<void>;
21
+ }
22
+ /** Binds generic persistence to a caller's path and normalization policy. */
23
+ export declare function createSidebarFile<T>(options: SidebarFileOptions<T>): SidebarFile<T>;
@@ -0,0 +1,84 @@
1
+ import { chmod, mkdir, readFile } from 'node:fs/promises';
2
+ import { dirname } from 'node:path';
3
+ import { WRITER_LOCK_CONSTANTS, withLock, writeJsonAtomic, } from '../fs/index.js';
4
+ /** Binds generic persistence to a caller's path and normalization policy. */
5
+ export function createSidebarFile(options) {
6
+ const { path, defaultValue, normalize } = options;
7
+ let chain = Promise.resolve();
8
+ const parse = (raw) => {
9
+ if (raw === '')
10
+ return defaultValue;
11
+ try {
12
+ return normalize(JSON.parse(raw));
13
+ }
14
+ catch {
15
+ return defaultValue;
16
+ }
17
+ };
18
+ const readRaw = async () => {
19
+ try {
20
+ return await readFile(path, 'utf8');
21
+ }
22
+ catch (error) {
23
+ if (error.code === 'ENOENT')
24
+ return '';
25
+ throw error;
26
+ }
27
+ };
28
+ const read = async () => {
29
+ try {
30
+ return parse(await readRaw());
31
+ }
32
+ catch {
33
+ return defaultValue;
34
+ }
35
+ };
36
+ const enqueue = (operation) => {
37
+ const result = chain.then(operation);
38
+ // Keep the next operation runnable without hiding this caller's rejection.
39
+ chain = result.catch(() => { });
40
+ return result;
41
+ };
42
+ const persist = async (merge, hooks) => {
43
+ const parent = dirname(path);
44
+ await mkdir(parent, { recursive: true, mode: 0o700 });
45
+ await chmod(parent, 0o700).catch((error) => {
46
+ options.logger?.warn('sidebar directory permission remediation failed', {
47
+ error: error instanceof Error ? error.message : String(error),
48
+ });
49
+ });
50
+ await withLock(path, {
51
+ ...WRITER_LOCK_CONSTANTS.sidebar,
52
+ timeoutMs: options.timeoutMs ?? WRITER_LOCK_CONSTANTS.sidebar.timeoutMs,
53
+ }, async (lock) => {
54
+ const commit = (value) => writeJsonAtomic(path, value, {
55
+ serialize: JSON.stringify,
56
+ beforeRename: async () => {
57
+ await hooks?.beforeCommit?.();
58
+ await lock.assertOwned();
59
+ },
60
+ });
61
+ // Older processes may ignore the lock; remerge if their bytes changed.
62
+ for (let attempt = 0; attempt < 3; attempt += 1) {
63
+ const raw = await readRaw();
64
+ const next = merge(parse(raw));
65
+ if (next === undefined)
66
+ return;
67
+ if (attempt === 0)
68
+ await hooks?.beforeRecheck?.();
69
+ if ((await readRaw()) !== raw)
70
+ continue;
71
+ await commit(next);
72
+ return;
73
+ }
74
+ const next = merge(await read());
75
+ if (next !== undefined)
76
+ await commit(next);
77
+ });
78
+ };
79
+ return {
80
+ read,
81
+ write: (value, hooks) => enqueue(() => persist(() => value, hooks)),
82
+ update: (merge, hooks) => enqueue(() => persist(merge, hooks)),
83
+ };
84
+ }
@@ -0,0 +1,8 @@
1
+ export interface LoadTuiOptions {
2
+ rawEntry: string;
3
+ runtimeEntry: string;
4
+ importModule?: (specifier: string) => Promise<{
5
+ default?: unknown;
6
+ }>;
7
+ }
8
+ export declare function loadTui({ rawEntry, runtimeEntry, importModule, }: LoadTuiOptions): Promise<unknown>;
@@ -0,0 +1,21 @@
1
+ import { isAbsolute } from 'node:path';
2
+ export async function loadTui({ rawEntry, runtimeEntry, importModule = (entry) => import(entry), }) {
3
+ for (const [option, entry] of Object.entries({ rawEntry, runtimeEntry })) {
4
+ if (!isAbsolute(entry) &&
5
+ !(entry.startsWith('file:') && new URL(entry).pathname.startsWith('/'))) {
6
+ throw new Error(`${option} must be an absolute path or file URL: ${entry}`);
7
+ }
8
+ }
9
+ let entry = runtimeEntry;
10
+ try {
11
+ await importModule(`opentui:runtime-module:${encodeURIComponent('@opentui/solid')}`);
12
+ }
13
+ catch (error) {
14
+ const message = error instanceof Error ? error.message : String(error);
15
+ if (!/Cannot find|Could not resolve|Module not found|Unable to resolve/.test(message) ||
16
+ !message.includes('opentui:runtime-module:'))
17
+ throw error;
18
+ entry = rawEntry;
19
+ }
20
+ return (await importModule(entry)).default;
21
+ }
@@ -0,0 +1,23 @@
1
+ export type TransformSolidSource = (code: string, options: {
2
+ filename: string;
3
+ moduleName: string;
4
+ resolvePath: (specifier: string) => string | null;
5
+ }) => Promise<string>;
6
+ export interface BuildTuiOptions {
7
+ inline?: (string | {
8
+ name: string;
9
+ root: string;
10
+ })[];
11
+ runtimeModules?: string[];
12
+ loadSolidTransform?: () => Promise<TransformSolidSource>;
13
+ }
14
+ export interface TuiBuildResult {
15
+ emitted: string[];
16
+ sources: string[];
17
+ selector: string;
18
+ externals: Set<string>;
19
+ }
20
+ export declare const runtimeModules: string[];
21
+ export declare const runtimeModuleId: (specifier: string) => string;
22
+ export declare function loadSolidTransform(): Promise<TransformSolidSource>;
23
+ export declare function buildTui(entryFile: string, variant: 'raw' | 'runtime', destinationDir: string, options?: BuildTuiOptions): Promise<TuiBuildResult>;
@@ -0,0 +1,190 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { existsSync, realpathSync } from 'node:fs';
3
+ import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';
4
+ import { createRequire } from 'node:module';
5
+ import { basename, dirname, extname, join, relative, resolve } from 'node:path';
6
+ import { fileURLToPath, pathToFileURL } from 'node:url';
7
+ import { isBareSpecifier, parseImports } from './walker.js';
8
+ export const runtimeModules = [
9
+ '@opentui/core',
10
+ '@opentui/core/testing',
11
+ '@opentui/solid',
12
+ '@opentui/solid/components',
13
+ '@opentui/solid/jsx-runtime',
14
+ '@opentui/solid/jsx-dev-runtime',
15
+ 'solid-js',
16
+ 'solid-js/store',
17
+ ];
18
+ export const runtimeModuleId = (specifier) => `opentui:runtime-module:${encodeURIComponent(specifier)}`;
19
+ export async function loadSolidTransform() {
20
+ const specifier = '@opentui/solid/scripts/solid-transform.js';
21
+ try {
22
+ return (await import(specifier)).transformSolidSource;
23
+ }
24
+ catch {
25
+ const require = createRequire(import.meta.url);
26
+ const root = dirname(require.resolve('@opentui/solid/package.json'));
27
+ return (await import(pathToFileURL(join(root, 'scripts/solid-transform.js')).href)).transformSolidSource;
28
+ }
29
+ }
30
+ function exportTarget(value) {
31
+ if (typeof value === 'string')
32
+ return value;
33
+ if (Array.isArray(value))
34
+ return value.map(exportTarget).find(Boolean);
35
+ if (value && typeof value === 'object') {
36
+ const conditions = value;
37
+ for (const [key, condition] of Object.entries(conditions)) {
38
+ if (!['bun', 'import', 'node', 'default'].includes(key))
39
+ continue;
40
+ const target = exportTarget(condition);
41
+ if (target)
42
+ return target;
43
+ }
44
+ }
45
+ return undefined;
46
+ }
47
+ export async function buildTui(entryFile, variant, destinationDir, options = {}) {
48
+ const entry = resolve(entryFile);
49
+ const destination = resolve(destinationDir);
50
+ const roots = new Map();
51
+ for (const item of options.inline ?? ['@cortexkit/common-auth']) {
52
+ if (typeof item !== 'string')
53
+ roots.set(item.name, resolve(item.root));
54
+ else {
55
+ let directory = dirname(entry);
56
+ for (;;) {
57
+ const manifest = join(directory, 'package.json');
58
+ if (existsSync(manifest) &&
59
+ JSON.parse(await readFile(manifest, 'utf8')).name === item) {
60
+ roots.set(item, realpathSync(directory));
61
+ break;
62
+ }
63
+ const installed = join(directory, 'node_modules', item);
64
+ if (existsSync(join(installed, 'package.json'))) {
65
+ roots.set(item, realpathSync(installed));
66
+ break;
67
+ }
68
+ const parent = dirname(directory);
69
+ if (parent === directory)
70
+ throw new Error(`Cannot locate package root for ${item} from ${entry}`);
71
+ directory = parent;
72
+ }
73
+ }
74
+ }
75
+ const resolveExport = async (specifier, root, name) => {
76
+ const manifest = JSON.parse(await readFile(join(root, 'package.json'), 'utf8'));
77
+ const subpath = specifier === name ? '.' : `.${specifier.slice(name.length)}`;
78
+ const exports = manifest.exports;
79
+ let target = exportTarget(exports?.[subpath] ?? (subpath === '.' ? exports : undefined));
80
+ if (!target && exports && typeof exports === 'object') {
81
+ const patterns = Object.keys(exports)
82
+ .filter((key) => key.includes('*'))
83
+ .sort((a, b) => b.indexOf('*') - a.indexOf('*') || b.length - a.length);
84
+ for (const pattern of patterns) {
85
+ const [prefix = '', suffix = ''] = pattern.split('*');
86
+ if (!subpath.startsWith(prefix) || !subpath.endsWith(suffix))
87
+ continue;
88
+ const wildcard = subpath.slice(prefix.length, suffix ? -suffix.length : undefined);
89
+ target = exportTarget(exports[pattern])?.replaceAll('*', wildcard);
90
+ break;
91
+ }
92
+ }
93
+ const absolute = target
94
+ ? resolve(root, target)
95
+ : `${root}/<missing exports ${subpath}>`;
96
+ if (!target || !existsSync(absolute))
97
+ throw new Error(`Cannot inline ${specifier}: resolved target ${absolute} does not exist`);
98
+ return realpathSync(absolute);
99
+ };
100
+ const transform = variant === 'runtime'
101
+ ? await (options.loadSolidTransform ?? loadSolidTransform)()
102
+ : undefined;
103
+ const runtime = new Set(options.runtimeModules ?? runtimeModules);
104
+ await rm(destination, { recursive: true, force: true });
105
+ await mkdir(destination, { recursive: true });
106
+ const result = {
107
+ emitted: [],
108
+ sources: [],
109
+ selector: 'selector.js',
110
+ externals: new Set(),
111
+ };
112
+ const copied = new Map();
113
+ const relativeImport = (from, to) => {
114
+ const path = relative(dirname(from), to).split('\\').join('/');
115
+ return path.startsWith('.') ? path : `./${path}`;
116
+ };
117
+ const visit = async (source, shared) => {
118
+ source = realpathSync(source);
119
+ if (relative(dirname(entry), source).startsWith('../'))
120
+ shared = true;
121
+ const previous = copied.get(source);
122
+ if (previous)
123
+ return previous;
124
+ const extension = variant === 'raw' && extname(source) === '.tsx' ? '.tsx' : '.js';
125
+ const name = shared
126
+ ? `shared/${createHash('sha256').update(source).digest('hex').slice(0, 16)}-${basename(source).replace(/\.[^.]+$/, '')}${extension}`
127
+ : relative(dirname(entry), source).replace(/\.[^.]+$/, extension);
128
+ if (name.startsWith('../'))
129
+ throw new Error(`Entry dependency escapes source directory: ${source}`);
130
+ copied.set(source, name);
131
+ let code = await readFile(source, 'utf8');
132
+ if (transform && /\.[jt]sx$/.test(source))
133
+ code = await transform(code, {
134
+ filename: source,
135
+ moduleName: runtimeModuleId('@opentui/solid'),
136
+ resolvePath: (specifier) => runtime.has(specifier) ? runtimeModuleId(specifier) : null,
137
+ });
138
+ const references = parseImports(code, source);
139
+ const replacements = [];
140
+ for (const reference of references) {
141
+ let specifier = reference.specifier;
142
+ const inline = [...roots].find(([name]) => specifier === name || specifier.startsWith(`${name}/`));
143
+ if (inline)
144
+ specifier = relativeImport(name, await visit(await resolveExport(specifier, inline[1], inline[0]), true));
145
+ else if (specifier.startsWith('.')) {
146
+ const base = resolve(dirname(source), specifier);
147
+ const candidates = [
148
+ base,
149
+ base.replace(/\.js$/, '.ts'),
150
+ base.replace(/\.js$/, '.tsx'),
151
+ `${base}.ts`,
152
+ `${base}.tsx`,
153
+ `${base}.js`,
154
+ join(base, 'index.ts'),
155
+ join(base, 'index.js'),
156
+ ];
157
+ const target = candidates.find(existsSync);
158
+ if (!target)
159
+ throw new Error(`Missing relative import in ${source}: ${specifier}`);
160
+ specifier = relativeImport(name, await visit(target, shared));
161
+ }
162
+ else if (variant === 'runtime' && runtime.has(specifier))
163
+ specifier = runtimeModuleId(specifier);
164
+ replacements.push({ ...reference, text: JSON.stringify(specifier) });
165
+ }
166
+ for (const replacement of replacements.reverse())
167
+ code =
168
+ code.slice(0, replacement.start) +
169
+ replacement.text +
170
+ code.slice(replacement.end);
171
+ if (/\.[cm]?ts$/.test(source))
172
+ code = new Bun.Transpiler({ loader: 'ts', target: 'bun' }).transformSync(code);
173
+ const output = join(destination, name);
174
+ await mkdir(dirname(output), { recursive: true });
175
+ await writeFile(output, code);
176
+ for (const reference of parseImports(code, source)) {
177
+ if (isBareSpecifier(reference.specifier))
178
+ result.externals.add(reference.specifier);
179
+ }
180
+ result.emitted.push(name);
181
+ result.sources.push(source);
182
+ return name;
183
+ };
184
+ await visit(entry, false);
185
+ const selectorSource = fileURLToPath(import.meta.resolve('@cortexkit/common-auth/tui'));
186
+ await writeFile(join(destination, result.selector), await readFile(selectorSource));
187
+ result.emitted.push(result.selector);
188
+ result.sources.push(selectorSource);
189
+ return result;
190
+ }
@@ -0,0 +1,3 @@
1
+ export type { BuildTuiOptions, TransformSolidSource, TuiBuildResult, } from './build-tui.js';
2
+ export { buildTui, loadSolidTransform, runtimeModuleId, runtimeModules, } from './build-tui.js';
3
+ export { assertEmittedPublishList } from './publish-list.js';
@@ -0,0 +1,2 @@
1
+ export { buildTui, loadSolidTransform, runtimeModuleId, runtimeModules, } from './build-tui.js';
2
+ export { assertEmittedPublishList } from './publish-list.js';
@@ -0,0 +1,2 @@
1
+ /** Compare the files npm actually publishes, not the manifest's glob patterns. */
2
+ export declare function assertEmittedPublishList(packageRoot: string, destinationDir: string, emitted: readonly string[]): Promise<void>;
@@ -0,0 +1,20 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { relative, resolve } from 'node:path';
3
+ import { promisify } from 'node:util';
4
+ /** Compare the files npm actually publishes, not the manifest's glob patterns. */
5
+ export async function assertEmittedPublishList(packageRoot, destinationDir, emitted) {
6
+ const { stdout } = await promisify(execFile)('npm', ['pack', '--json', '--dry-run', '--ignore-scripts'], { cwd: packageRoot });
7
+ const packed = JSON.parse(stdout);
8
+ const prefix = `${relative(resolve(packageRoot), resolve(destinationDir)).split('\\').join('/')}/`;
9
+ const packageInfo = packed[0];
10
+ if (!packageInfo)
11
+ throw new Error('npm pack returned no package');
12
+ const actual = packageInfo.files
13
+ .map((file) => file.path)
14
+ .filter((path) => path.startsWith(prefix))
15
+ .map((path) => path.slice(prefix.length))
16
+ .sort();
17
+ const expected = [...emitted].sort();
18
+ if (JSON.stringify(actual) !== JSON.stringify(expected))
19
+ throw new Error(`Published destination differs from emitted: ${JSON.stringify({ actual, expected })}`);
20
+ }
@@ -0,0 +1,8 @@
1
+ export interface ImportReference {
2
+ specifier: string;
3
+ start: number;
4
+ end: number;
5
+ }
6
+ /** Locate module strings without treating comments or ordinary strings as imports. */
7
+ export declare function parseImports(code: string, filename: string): ImportReference[];
8
+ export declare function isBareSpecifier(specifier: string): boolean;