Skip to content

Commit

Permalink
Overhaul repo config handling (srcbookdev#247)
Browse files Browse the repository at this point in the history
- standardize ts configs
- stricten up null/undef handling
- CLI moved from js to ts
  • Loading branch information
versecafe authored Sep 3, 2024
1 parent b3db1de commit 00be017
Show file tree
Hide file tree
Showing 33 changed files with 259 additions and 139 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
"devDependencies": {
"@types/node": "catalog:",
"prettier": "catalog:",
"typescript": "catalog:"
"typescript": "catalog:",
"@srcbook/configs": "workspace:*"
},
"dependencies": {
"turbo": "^2.0.14"
Expand Down
13 changes: 10 additions & 3 deletions packages/api/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,11 @@ export async function getConfig(): Promise<Config> {
if (results.length !== 1) {
console.warn('Expected exactly one config record, found:', results.length);
}

return results[0];
if (results.length === 0) {
throw new Error('No config found');
}
// explicitly known that a config exists here
return results[0] as Config;
}

export async function updateConfig(attrs: Partial<Config>) {
Expand All @@ -55,7 +58,11 @@ export async function addSecret(name: string, value: string): Promise<Secret> {
.values({ name, value })
.onConflictDoUpdate({ target: secrets.name, set: { value } })
.returning();
return result[0];
if (result.length === 0) {
throw new Error('No secret returned');
}
// explicitly known that a config exists here
return result[0] as Secret;
}

export async function removeSecret(name: string) {
Expand Down
6 changes: 5 additions & 1 deletion packages/api/session.mts
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,11 @@ export async function exportSrcmdFile(session: SessionType, destinationPath: str
}

export async function findSession(id: string): Promise<SessionType> {
return sessions[id];
if (!sessions[id]) {
throw new Error(`Session with id ${id} not found`);
}
// explicitly known that a session exists here
return sessions[id] as SessionType;
}

type UpdateResultType =
Expand Down
23 changes: 12 additions & 11 deletions packages/api/srcmd/decoding.mts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ function getSrcbookMetadata(tokens: TokensList) {
}

try {
const metadata = JSON.parse(match[1]);
const metadata = JSON.parse(match[1] ?? '');

const filteredTokens = tokens.filter((t) => t !== srcbookMetdataToken);

Expand Down Expand Up @@ -169,14 +169,15 @@ function groupTokens(tokens: Token[]) {
}

function isLink(token: Tokens.Paragraph) {
return token.tokens.length === 1 && token.tokens[0].type === 'link';
return token.tokens.length === 1 && token.tokens[0]?.type === 'link';
}

let i = 0;
const len = tokens.length;

while (i < len) {
const token = tokens[i];
if (!token) continue;

switch (token.type) {
case 'heading':
Expand Down Expand Up @@ -217,7 +218,7 @@ function groupTokens(tokens: Token[]) {
function validateTokenGroups(grouped: GroupedTokensType[]) {
const errors: string[] = [];

const firstGroupIsTitle = grouped[0].type === 'title';
const firstGroupIsTitle = grouped[0]?.type === 'title';
const hasOnlyOneTitle = grouped.filter((group) => group.type === 'title').length === 1;
const invalidTitle = !(firstGroupIsTitle && hasOnlyOneTitle);
const hasAtMostOnePackageJson =
Expand All @@ -238,8 +239,8 @@ function validateTokenGroups(grouped: GroupedTokensType[]) {
while (i < len) {
const group = grouped[i];

if (group.type === 'filename') {
if (!['code', 'code:linked'].includes(grouped[i + 1].type)) {
if (group?.type === 'filename') {
if (!['code', 'code:linked'].includes(grouped[i + 1]?.type ?? '')) {
const raw = group.token.raw.trimEnd();
errors.push(`h6 is reserved for code cells, but no code block followed '${raw}'`);
} else {
Expand All @@ -262,8 +263,8 @@ function validateTokenGroupsPartial(grouped: GroupedTokensType[]) {
while (i < len) {
const group = grouped[i];

if (group.type === 'filename') {
if (!['code', 'code:linked'].includes(grouped[i + 1].type)) {
if (group?.type === 'filename') {
if (!['code', 'code:linked'].includes(grouped[i + 1]?.type ?? '')) {
const raw = group.token.raw.trimEnd();
errors.push(`h6 is reserved for code cells, but no code block followed '${raw}'`);
} else {
Expand All @@ -286,19 +287,19 @@ function convertToCells(groups: GroupedTokensType[]): CellType[] {
while (i < len) {
const group = groups[i];

if (group.type === 'title') {
if (group?.type === 'title') {
cells.push(convertTitle(group.token));
} else if (group.type === 'markdown') {
} else if (group?.type === 'markdown') {
const hasNonSpaceTokens = group.tokens.some((token) => token.type !== 'space');
// This shouldn't happen under most conditions, but there could be cases where there
// is excess whitespace, causing markdown blocks that were not intentional. Thus, we
// only create markdown cells when the markdown contains more than just space tokens.
if (hasNonSpaceTokens) {
cells.push(convertMarkdown(group.tokens));
}
} else if (group.type === 'filename') {
} else if (group?.type === 'filename') {
i += 1;
switch (groups[i].type) {
switch (groups[i]?.type) {
case 'code': {
const codeToken = (groups[i] as CodeGroupType).token;
const filename = group.token.text;
Expand Down
22 changes: 5 additions & 17 deletions packages/api/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,23 +1,11 @@
{
"extends": "../../tsconfig.base.json",
"extends": "@srcbook/configs/ts/base.json",
"compilerOptions": {
"module": "nodenext",
"target": "es2022",
"moduleResolution": "nodenext",
"esModuleInterop": true,
"isolatedModules": true,
"forceConsistentCasingInFileNames": true,
"declaration": true,
"composite": true,
"sourceMap": true,
"declarationMap": true,
"outDir": "./dist",
"noImplicitOverride": true,
"rootDir": "./",
"allowSyntheticDefaultImports": true,
"verbatimModuleSyntax": true,
"noEmit": false,
"allowImportingTsExtensions": false,
"outDir": "dist",
"types": ["vitest/globals"]
},
"include": ["./**/*.mts"],
"include": ["**/*.mts"],
"exclude": ["node_modules", "dist"]
}
4 changes: 2 additions & 2 deletions packages/api/tsserver/messages.mts
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,12 @@ function getContentByteLength(buffer: Uint8Array): { start: number; byteLength:
return null;
}

if (buffer[i] === CARRIAGE_RETURN_CHAR_CODE) {
if (buffer[i] === CARRIAGE_RETURN_CHAR_CODE || buffer[i] === undefined) {
break;
}

// If the character is not a number (codes 48-57), the data in the buffer is invalid.
if (buffer[i] < 48 || buffer[i] > 57) {
if ((buffer[i] as number) < 48 || (buffer[i] as number) > 57) {
throw new Error(
`Unexpected byte '${buffer[i]}' in Content-Length header. Expected a number between 0 and 9 (byte values 48-57).`,
);
Expand Down
5 changes: 5 additions & 0 deletions packages/configs/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "@srcbook/configs",
"version": "0.0.0",
"private": true
}
26 changes: 26 additions & 0 deletions packages/configs/ts/base.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"display": "Default",
"compilerOptions": {
"declaration": true,
"declarationMap": true,
"esModuleInterop": true,
"incremental": false,
"isolatedModules": true,
"useDefineForClassFields": true,
"lib": ["es2022", "DOM", "DOM.Iterable"],
"module": "Preserve",
"moduleDetection": "force",
"moduleResolution": "bundler",
"noUncheckedIndexedAccess": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"strict": true,
"target": "ES2022",
"noEmit": true,
"allowImportingTsExtensions": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
}
}
8 changes: 8 additions & 0 deletions packages/configs/ts/react-library.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"display": "React Library",
"extends": "./base.json",
"compilerOptions": {
"jsx": "react-jsx"
}
}
16 changes: 8 additions & 8 deletions packages/shared/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
export * from './src/schemas/cells.js';
export * from './src/schemas/tsserver.js';
export * from './src/schemas/websockets.js';
export * from './src/types/cells.js';
export * from './src/types/tsserver.js';
export * from './src/types/websockets.js';
export * from './src/utils.js';
export * from './src/ai.js';
export * from './src/schemas/cells';
export * from './src/schemas/tsserver';
export * from './src/schemas/websockets';
export * from './src/types/cells';
export * from './src/types/tsserver';
export * from './src/types/websockets';
export * from './src/utils';
export * from './src/ai';
4 changes: 2 additions & 2 deletions packages/shared/src/schemas/websockets.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import z from 'zod';
import { CellSchema, MarkdownCellSchema, CodeCellSchema, CellUpdateAttrsSchema } from './cells.js';
import { CellSchema, MarkdownCellSchema, CodeCellSchema, CellUpdateAttrsSchema } from './cells';
import {
TsServerDiagnosticSchema,
TsServerQuickInfoRequestSchema,
TsServerQuickInfoResponseSchema,
} from './tsserver.js';
} from './tsserver';

// A _message_ over websockets
export const WebSocketMessageSchema = z.tuple([
Expand Down
2 changes: 1 addition & 1 deletion packages/shared/src/types/cells.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
CodeCellUpdateAttrsSchema,
CellUpdateAttrsSchema,
SrcbookMetadataSchema,
} from '../schemas/cells.js';
} from '../schemas/cells';

export type TitleCellType = z.infer<typeof TitleCellSchema>;
export type MarkdownCellType = z.infer<typeof MarkdownCellSchema>;
Expand Down
2 changes: 1 addition & 1 deletion packages/shared/src/types/websockets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import {
TsServerCellSuggestionsPayloadSchema,
TsServerQuickInfoRequestPayloadSchema,
TsServerQuickInfoResponsePayloadSchema,
} from '../schemas/websockets.js';
} from '../schemas/websockets';

export type CellExecPayloadType = z.infer<typeof CellExecPayloadSchema>;
export type CellStopPayloadType = z.infer<typeof CellStopPayloadSchema>;
Expand Down
2 changes: 1 addition & 1 deletion packages/shared/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { base32hexnopad } from '@scure/base';
import type { CodeLanguageType } from './types/cells.js';
import type { CodeLanguageType } from './types/cells';
import * as crypto from 'crypto';

export function isBrowser(): boolean {
Expand Down
20 changes: 5 additions & 15 deletions packages/shared/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,20 +1,10 @@
{
"extends": "../../tsconfig.base.json",
"extends": "@srcbook/configs/ts/base.json",
"compilerOptions": {
"module": "nodenext",
"target": "es2022",
"moduleResolution": "nodenext",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"declaration": true,
"composite": true,
"sourceMap": true,
"declarationMap": true,
"outDir": "./dist",
"verbatimModuleSyntax": true,
"rootDir": "./",
"allowSyntheticDefaultImports": true
"noEmit": false,
"allowImportingTsExtensions": false,
"outDir": "dist"
},
"include": ["./index.ts", "./src/**/*.ts"],
"include": ["src", "index.ts", "tests"],
"exclude": ["node_modules", "dist"]
}
2 changes: 1 addition & 1 deletion packages/web/src/clients/websocket/channel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export default class Channel<

on<K extends keyof I & string>(event: K, callback: (payload: z.TypeOf<I[K]>) => void): void {
this.callbacks[event] = this.callbacks[event] || [];
this.callbacks[event].push(callback);
this.callbacks[event]?.push(callback);
}

off<K extends keyof I & string>(event: K, callback: (payload: z.TypeOf<I[K]>) => void): void {
Expand Down
2 changes: 1 addition & 1 deletion packages/web/src/clients/websocket/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export default class WebSocketClient {

on(topic: string, callback: (event: string, payload: Record<string, any>) => void) {
this.callbacks[topic] = this.callbacks[topic] || [];
this.callbacks[topic].push(callback);
this.callbacks[topic]?.push(callback);
}

off(topic: string, callback: (event: string, payload: Record<string, any>) => void) {
Expand Down
4 changes: 2 additions & 2 deletions packages/web/src/components/cells/hover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ export function tsHover(sessionId: string, cell: CodeCellType, channel: SessionC
let start = pos;
let end = pos;

while (start > from && /\w/.test(text[start - from - 1])) start--;
while (end < to && /\w/.test(text[end - from])) end++;
while (start > from && /\w/.test(text[start - from - 1] ?? '')) start--;
while (end < to && /\w/.test(text[end - from] ?? '')) end++;

return {
pos: start,
Expand Down
4 changes: 2 additions & 2 deletions packages/web/src/components/cells/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ export function mapCMLocationToTsServer(
let remainingPosition = cmPosition;
let lineIndex = 0;

while (lineIndex < lines.length && remainingPosition > lines[lineIndex].length) {
remainingPosition -= lines[lineIndex].length + 1; // +1 for newline character
while (lineIndex < lines.length && remainingPosition > (lines[lineIndex]?.length ?? 0)) {
remainingPosition -= (lines[lineIndex]?.length ?? 0) + 1; // +1 for newline character
lineIndex++;
}

Expand Down
2 changes: 1 addition & 1 deletion packages/web/src/components/drag-and-drop-srcmd-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export function DragAndDropSrcmdModal(props: { children: React.ReactNode }) {
const file = files[0];

// TODO: Error handling
if (!file.name.endsWith('.src.md')) {
if (!file?.name.endsWith('.src.md')) {
return;
}

Expand Down
6 changes: 3 additions & 3 deletions packages/web/src/components/install-package-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ function getSelected(results: NPMPackageType[], selectedName: string, type: 'nex
const len = results.length;

if (selectedIdx < 0) {
return results[len - 1].name;
return results[len - 1]?.name ?? null;
} else if (selectedIdx >= len) {
return results[0].name;
return results[0]?.name ?? null;
} else {
return results[selectedIdx].name;
return results[selectedIdx]?.name ?? null;
}
}

Expand Down
4 changes: 2 additions & 2 deletions packages/web/src/components/ui/heading.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export function EditableH1(props: {
if (result.success) {
props.onUpdated(result.data.text);
} else {
setError(result.error.errors[0].message);
setError(result.error.errors[0]?.message ?? 'Unknown error');
if (ref.current) {
ref.current.innerText = props.text;
}
Expand All @@ -67,7 +67,7 @@ export function EditableH1(props: {
text: ref.current.innerText + e.key,
});
if (result.error) {
setError(result.error.errors[0].message);
setError(result.error.errors[0]?.message ?? 'Unknown error');
e.preventDefault();
return false;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/web/src/routes/secrets.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useRef, useState } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { cn } from '@/lib/utils';
import { cn } from '@/lib/utils.ts';
import { getSecrets } from '@/lib/server';
import { Info, Trash2, Eye, EyeOff } from 'lucide-react';
import { Form, useLoaderData, useRevalidator } from 'react-router-dom';
Expand Down
1 change: 1 addition & 0 deletions packages/web/src/routes/session.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ function Session(props: { session: SessionType; channel: SessionChannel; config:
async function insertGeneratedCells(idx: number, cells: Array<CodeCellType | MarkdownCellType>) {
for (let i = 0; i < cells.length; i++) {
const cell = cells[i];
if (!cell) continue;
const insertIdx = idx + i;
let newCell;
switch (cell.type) {
Expand Down
Loading

0 comments on commit 00be017

Please sign in to comment.