chore(scaffold): bootstrap docmost-sync Node/TS project skeleton
Set up the project structure per the new-project guide, adapted from the Python skeleton to the Node/TS stack fixed in SPEC.md (reuses docmost-mcp). Scaffold only — the sync engine is not implemented yet. - src/settings.ts: single config layer on zod, schema keyed by real ENV names; credentials and own-service address have no default (fail fast). - src/config-errors.ts: loadSettingsOrExit — clear startup message naming the missing/invalid env var instead of a raw stack trace; exit(1). - src/index.ts: thin entry point that validates config and logs (stub). - test/: vitest unit tests for settings parsing and config errors (10 tests). - Makefile (install/env/build/test/run/dev/clean), strict tsconfig, vitest. - Dockerfile (single-stage, no EXPOSE, prunes dev deps), docker-compose (daemon, volume on /app/data, watchtower), ghcr CI with build needs test. - .env.example, .gitignore/.dockerignore, AGENTS.md, README.md. - Pinned deps (dotenv, zod) + committed package-lock.json.
This commit is contained in:
76
test/settings.test.ts
Normal file
76
test/settings.test.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { parseSettings } from '../src/settings.js';
|
||||
|
||||
// A minimal valid environment with every required variable set. Tests clone and
|
||||
// mutate this object so process.env is never touched (hermetic).
|
||||
const baseEnv = {
|
||||
DOCMOST_API_URL: 'https://docmost.example.com',
|
||||
DOCMOST_EMAIL: 'you@example.com',
|
||||
DOCMOST_PASSWORD: 'secret',
|
||||
DOCMOST_SPACE_ID: 'space-123',
|
||||
} as NodeJS.ProcessEnv;
|
||||
|
||||
describe('parseSettings', () => {
|
||||
it('maps a full valid env to the camelCase Settings object', () => {
|
||||
const settings = parseSettings({
|
||||
...baseEnv,
|
||||
VAULT_PATH: 'data/custom-vault',
|
||||
GIT_REMOTE: 'git@github.com:you/vault.git',
|
||||
POLL_INTERVAL_MS: '5000',
|
||||
DEBOUNCE_MS: '1000',
|
||||
LOG_LEVEL: 'debug',
|
||||
});
|
||||
|
||||
expect(settings).toEqual({
|
||||
docmostApiUrl: 'https://docmost.example.com',
|
||||
docmostEmail: 'you@example.com',
|
||||
docmostPassword: 'secret',
|
||||
docmostSpaceId: 'space-123',
|
||||
vaultPath: 'data/custom-vault',
|
||||
gitRemote: 'git@github.com:you/vault.git',
|
||||
pollIntervalMs: 5000,
|
||||
debounceMs: 1000,
|
||||
logLevel: 'debug',
|
||||
});
|
||||
});
|
||||
|
||||
it('applies defaults when optional vars are omitted', () => {
|
||||
const settings = parseSettings({ ...baseEnv });
|
||||
|
||||
expect(settings.vaultPath).toBe('data/vault');
|
||||
expect(settings.pollIntervalMs).toBe(15000);
|
||||
expect(settings.debounceMs).toBe(2000);
|
||||
expect(settings.logLevel).toBe('info');
|
||||
expect(settings.gitRemote).toBeUndefined();
|
||||
});
|
||||
|
||||
it('coerces numeric strings to numbers', () => {
|
||||
const settings = parseSettings({ ...baseEnv, POLL_INTERVAL_MS: '3000' });
|
||||
|
||||
expect(settings.pollIntervalMs).toBe(3000);
|
||||
expect(typeof settings.pollIntervalMs).toBe('number');
|
||||
});
|
||||
|
||||
it('throws when a required var is missing', () => {
|
||||
const { DOCMOST_API_URL: _omit, ...rest } = baseEnv;
|
||||
void _omit;
|
||||
expect(() => parseSettings(rest as NodeJS.ProcessEnv)).toThrow();
|
||||
});
|
||||
|
||||
it('throws on an invalid LOG_LEVEL', () => {
|
||||
expect(() =>
|
||||
parseSettings({ ...baseEnv, LOG_LEVEL: 'verbose' }),
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
it('throws on a non-numeric POLL_INTERVAL_MS', () => {
|
||||
expect(() =>
|
||||
parseSettings({ ...baseEnv, POLL_INTERVAL_MS: 'soon' }),
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
it('treats an empty GIT_REMOTE as undefined', () => {
|
||||
const settings = parseSettings({ ...baseEnv, GIT_REMOTE: '' });
|
||||
expect(settings.gitRemote).toBeUndefined();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user