@observablehq/notebook-kit 1.3.0-rc.3 → 1.4.0-rc.1
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.
- package/dist/package.json +2 -2
- package/dist/src/javascript/assignments.d.ts +6 -1
- package/dist/src/javascript/assignments.js +25 -14
- package/dist/src/javascript/assignments.test.js +16 -3
- package/dist/src/javascript/imports.d.ts +3 -1
- package/dist/src/javascript/imports.js +5 -3
- package/dist/src/javascript/parse.js +1 -6
- package/dist/src/javascript/references.d.ts +2 -1
- package/dist/src/javascript/references.js +8 -3
- package/dist/src/javascript/template.js +17 -11
- package/dist/src/javascript/transpile.js +9 -6
- package/dist/src/lib/interpreters.js +4 -0
- package/dist/src/lib/notebook.d.ts +2 -2
- package/dist/src/lib/notebook.js +1 -1
- package/dist/src/lib/serialize.js +4 -0
- package/package.json +2 -2
package/dist/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/observablehq/notebook-kit.git"
|
|
7
7
|
},
|
|
8
|
-
"version": "1.
|
|
8
|
+
"version": "1.4.0-rc.1",
|
|
9
9
|
"type": "module",
|
|
10
10
|
"scripts": {
|
|
11
11
|
"test": "vitest",
|
|
@@ -61,6 +61,7 @@
|
|
|
61
61
|
"jsdom": "^26.1.0",
|
|
62
62
|
"markdown-it": "^14.1.0",
|
|
63
63
|
"markdown-it-anchor": "^9.2.0",
|
|
64
|
+
"typescript": "^5.8.3",
|
|
64
65
|
"vite": "^7.0.0"
|
|
65
66
|
},
|
|
66
67
|
"devDependencies": {
|
|
@@ -77,7 +78,6 @@
|
|
|
77
78
|
"postgres": "^3.4.7",
|
|
78
79
|
"snowflake-sdk": "^2.1.3",
|
|
79
80
|
"tsx": "^4.20.3",
|
|
80
|
-
"typescript": "^5.8.3",
|
|
81
81
|
"typescript-eslint": "^8.35.0",
|
|
82
82
|
"vitest": "^3.2.4"
|
|
83
83
|
},
|
|
@@ -1,2 +1,7 @@
|
|
|
1
1
|
import type { Identifier, Node } from "acorn";
|
|
2
|
-
export declare function checkAssignments(node: Node,
|
|
2
|
+
export declare function checkAssignments(node: Node, { input, locals, references, globals }: {
|
|
3
|
+
input: string;
|
|
4
|
+
locals: Map<Node, Set<string>>;
|
|
5
|
+
globals?: Set<string>;
|
|
6
|
+
references: Identifier[];
|
|
7
|
+
}): void;
|
|
@@ -1,33 +1,44 @@
|
|
|
1
1
|
import { defaultGlobals } from "./globals.js";
|
|
2
2
|
import { syntaxError } from "./syntaxError.js";
|
|
3
|
-
import {
|
|
4
|
-
export function checkAssignments(node, references,
|
|
5
|
-
function
|
|
3
|
+
import { ancestor } from "./walk.js";
|
|
4
|
+
export function checkAssignments(node, { input, locals, references, globals = defaultGlobals }) {
|
|
5
|
+
function isLocal({ name }, parents) {
|
|
6
|
+
for (const p of parents)
|
|
7
|
+
if (locals.get(p)?.has(name))
|
|
8
|
+
return true;
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
function checkConst(node, parents) {
|
|
6
12
|
switch (node.type) {
|
|
7
13
|
case "Identifier":
|
|
14
|
+
if (isLocal(node, parents))
|
|
15
|
+
break;
|
|
8
16
|
if (references.includes(node))
|
|
9
17
|
throw syntaxError(`Assignment to external variable '${node.name}'`, node, input);
|
|
10
|
-
if (
|
|
18
|
+
if (globals.has(node.name))
|
|
11
19
|
throw syntaxError(`Assignment to global '${node.name}'`, node, input);
|
|
12
20
|
break;
|
|
13
|
-
case "ObjectPattern":
|
|
14
|
-
node.properties.forEach((node) => checkConst(node.type === "Property" ? node.value : node));
|
|
15
|
-
break;
|
|
16
21
|
case "ArrayPattern":
|
|
17
|
-
|
|
22
|
+
for (const e of node.elements)
|
|
23
|
+
if (e)
|
|
24
|
+
checkConst(e, parents);
|
|
25
|
+
break;
|
|
26
|
+
case "ObjectPattern":
|
|
27
|
+
for (const p of node.properties)
|
|
28
|
+
checkConst(p.type === "Property" ? p.value : p, parents);
|
|
18
29
|
break;
|
|
19
30
|
case "RestElement":
|
|
20
|
-
checkConst(node.argument);
|
|
31
|
+
checkConst(node.argument, parents);
|
|
21
32
|
break;
|
|
22
33
|
}
|
|
23
34
|
}
|
|
24
|
-
function
|
|
25
|
-
checkConst(
|
|
35
|
+
function checkConstArgument({ argument }, parents) {
|
|
36
|
+
checkConst(argument, parents);
|
|
26
37
|
}
|
|
27
|
-
function
|
|
28
|
-
checkConst(
|
|
38
|
+
function checkConstLeft({ left }, parents) {
|
|
39
|
+
checkConst(left, parents);
|
|
29
40
|
}
|
|
30
|
-
|
|
41
|
+
ancestor(node, {
|
|
31
42
|
AssignmentExpression: checkConstLeft,
|
|
32
43
|
AssignmentPattern: checkConstLeft,
|
|
33
44
|
UpdateExpression: checkConstArgument,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { assert, test } from "vitest";
|
|
2
|
-
import { checkAssignments } from "./assignments.js";
|
|
3
2
|
import { parseJavaScript } from "./parse.js";
|
|
3
|
+
import { findReferences } from "./references.js";
|
|
4
4
|
function check(input) {
|
|
5
5
|
const cell = parseJavaScript(input);
|
|
6
|
-
|
|
6
|
+
findReferences(cell.body, { input });
|
|
7
7
|
}
|
|
8
8
|
test("allows non-external assignments", () => {
|
|
9
9
|
assert.doesNotThrow(() => check("let foo = 1;\nfoo = 2;"));
|
|
@@ -29,5 +29,18 @@ test("does not allow external assignments via for…of or for…in", () => {
|
|
|
29
29
|
assert.throws(() => check("for (foo in {});"), /external variable 'foo'/);
|
|
30
30
|
});
|
|
31
31
|
test("does not allow global assignments", () => {
|
|
32
|
-
assert.throws(() => check("window = 1;"), /global 'window'/);
|
|
32
|
+
assert.throws(() => check("window = 1;"), /Assignment to global 'window'/);
|
|
33
|
+
assert.throws(() => check("const foo = (window = 1);"), /Assignment to global 'window'/);
|
|
34
|
+
});
|
|
35
|
+
test("does not allow conflicting top-level variables", () => {
|
|
36
|
+
assert.throws(() => check("const window = 1;"), /Global 'window' cannot be redefined/);
|
|
37
|
+
assert.throws(() => check("const foo = 1, window = 2;"), /Global 'window' cannot be redefined/);
|
|
38
|
+
assert.throws(() => check("const {window} = {};"), /Global 'window' cannot be redefined/);
|
|
39
|
+
assert.throws(() => check("const {window = 1} = {};"), /Global 'window' cannot be redefined/);
|
|
40
|
+
});
|
|
41
|
+
test("allows conflicting non-top-level variables", () => {
|
|
42
|
+
assert.doesNotThrow(() => check("{ const window = 1; }"));
|
|
43
|
+
assert.doesNotThrow(() => check("{ let window; window = 2; }"));
|
|
44
|
+
assert.doesNotThrow(() => check("{ const {window} = {}; }"));
|
|
45
|
+
assert.doesNotThrow(() => check("{ const {window = 1} = {}; }"));
|
|
33
46
|
});
|
|
@@ -3,7 +3,9 @@ import type { ImportDefaultSpecifier, ImportSpecifier } from "acorn";
|
|
|
3
3
|
import type { Sourcemap } from "./sourcemap.js";
|
|
4
4
|
type NamedImportSpecifier = ImportSpecifier | ImportDefaultSpecifier;
|
|
5
5
|
/** Throws a syntax error if any export declarations are found. */
|
|
6
|
-
export declare function checkExports(body: Node, input
|
|
6
|
+
export declare function checkExports(body: Node, { input }: {
|
|
7
|
+
input: string;
|
|
8
|
+
}): void;
|
|
7
9
|
/** Returns true if the body includes an import declaration. */
|
|
8
10
|
export declare function hasImportDeclaration(body: Node): boolean;
|
|
9
11
|
/** Returns true if the given node is a import.meta.resolve(…) call. */
|
|
@@ -6,7 +6,7 @@ import { getStringLiteralValue, isStringLiteral } from "./strings.js";
|
|
|
6
6
|
import { syntaxError } from "./syntaxError.js";
|
|
7
7
|
import { simple } from "./walk.js";
|
|
8
8
|
/** Throws a syntax error if any export declarations are found. */
|
|
9
|
-
export function checkExports(body, input) {
|
|
9
|
+
export function checkExports(body, { input }) {
|
|
10
10
|
function checkExport(child) {
|
|
11
11
|
throw syntaxError("Unexpected token 'export'", child, input);
|
|
12
12
|
}
|
|
@@ -109,8 +109,10 @@ function renderImport(source, node, input) {
|
|
|
109
109
|
return `import(${source}${node.attributes.length > 0
|
|
110
110
|
? `, {with: {${input.slice(node.attributes[0].start, node.attributes[node.attributes.length - 1].end)}}}`
|
|
111
111
|
: ""})${names.length > 0
|
|
112
|
-
? `.then((module) => {${names
|
|
113
|
-
|
|
112
|
+
? `.then((module) => {${names
|
|
113
|
+
.map((name) => `
|
|
114
|
+
if (!("${name}" in module)) throw new SyntaxError(\`export '${name}' not found\`);`)
|
|
115
|
+
.join("")}
|
|
114
116
|
return module;
|
|
115
117
|
})`
|
|
116
118
|
: ""}`;
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { Parser, tokTypes } from "acorn";
|
|
2
|
-
import { checkExports } from "./imports.js";
|
|
3
2
|
import { findReferences } from "./references.js";
|
|
4
|
-
import { checkAssignments } from "./assignments.js";
|
|
5
3
|
import { findDeclarations } from "./declarations.js";
|
|
6
4
|
import { findAwaits } from "./awaits.js";
|
|
7
5
|
export const acornOptions = {
|
|
@@ -25,13 +23,10 @@ export function parseJavaScript(input) {
|
|
|
25
23
|
if (expression?.type === "FunctionExpression" && expression.id)
|
|
26
24
|
expression = null; // treat named function as program
|
|
27
25
|
const body = expression ?? parseProgram(input); // otherwise parse as a program
|
|
28
|
-
checkExports(body, input);
|
|
29
|
-
const references = findReferences(body);
|
|
30
|
-
checkAssignments(body, references, input);
|
|
31
26
|
return {
|
|
32
27
|
body,
|
|
33
28
|
declarations: expression ? null : findDeclarations(body, input),
|
|
34
|
-
references,
|
|
29
|
+
references: findReferences(body, { input }),
|
|
35
30
|
expression: !!expression,
|
|
36
31
|
async: findAwaits(body).length > 0
|
|
37
32
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Identifier, Node } from "acorn";
|
|
2
|
-
export declare function findReferences(node: Node, { globals, filterReference, filterDeclaration }?: {
|
|
2
|
+
export declare function findReferences(node: Node, { input, globals, filterReference, filterDeclaration }?: {
|
|
3
|
+
input?: string;
|
|
3
4
|
globals?: Set<string>;
|
|
4
5
|
filterReference?: (identifier: Identifier) => boolean;
|
|
5
6
|
filterDeclaration?: (identifier: {
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { checkAssignments } from "./assignments.js";
|
|
1
2
|
import { defaultGlobals } from "./globals.js";
|
|
3
|
+
import { checkExports } from "./imports.js";
|
|
2
4
|
import { ancestor } from "./walk.js";
|
|
3
5
|
function isScope(node) {
|
|
4
6
|
return (node.type === "FunctionExpression" ||
|
|
@@ -15,12 +17,11 @@ function isBlockScope(node) {
|
|
|
15
17
|
node.type === "ForStatement" ||
|
|
16
18
|
isScope(node));
|
|
17
19
|
}
|
|
18
|
-
export function findReferences(node, { globals = defaultGlobals, filterReference = (identifier) => !globals.has(identifier.name), filterDeclaration = () => true } = {}) {
|
|
20
|
+
export function findReferences(node, { input, globals = defaultGlobals, filterReference = (identifier) => !globals.has(identifier.name), filterDeclaration = () => true } = {}) {
|
|
19
21
|
const locals = new Map();
|
|
20
22
|
const references = [];
|
|
21
23
|
function hasLocal(node, name) {
|
|
22
|
-
|
|
23
|
-
return l ? l.has(name) : false;
|
|
24
|
+
return locals.get(node)?.has(name) ?? false;
|
|
24
25
|
}
|
|
25
26
|
function declareLocal(node, id) {
|
|
26
27
|
if (!filterDeclaration(id))
|
|
@@ -125,5 +126,9 @@ export function findReferences(node, { globals = defaultGlobals, filterReference
|
|
|
125
126
|
},
|
|
126
127
|
Identifier: identifier
|
|
127
128
|
});
|
|
129
|
+
if (input !== undefined) {
|
|
130
|
+
checkAssignments(node, { locals, references, globals, input });
|
|
131
|
+
checkExports(node, { input });
|
|
132
|
+
}
|
|
128
133
|
return references;
|
|
129
134
|
}
|
|
@@ -72,12 +72,19 @@ export function parseTemplate(input) {
|
|
|
72
72
|
}
|
|
73
73
|
export function transpileTemplate(input, tag = "", raw = false) {
|
|
74
74
|
let cell;
|
|
75
|
+
let prefix;
|
|
76
|
+
let suffix;
|
|
75
77
|
if (typeof input !== "string") {
|
|
76
78
|
cell = input;
|
|
77
79
|
input = cell.value;
|
|
78
|
-
|
|
80
|
+
prefix = getPrefix(cell);
|
|
81
|
+
suffix = getSuffix(cell);
|
|
79
82
|
raw = getRaw(cell);
|
|
80
83
|
}
|
|
84
|
+
else {
|
|
85
|
+
prefix = tag;
|
|
86
|
+
suffix = "";
|
|
87
|
+
}
|
|
81
88
|
if (!input)
|
|
82
89
|
return input;
|
|
83
90
|
const source = new Sourcemap(input);
|
|
@@ -93,30 +100,29 @@ export function transpileTemplate(input, tag = "", raw = false) {
|
|
|
93
100
|
(raw ? escapeRawTemplateElements : escapeTemplateElements)(source, template);
|
|
94
101
|
node = template;
|
|
95
102
|
}
|
|
96
|
-
source.insertLeft(node.start,
|
|
97
|
-
source.insertRight(node.end,
|
|
98
|
-
source
|
|
99
|
-
return String(source) + (cell ? getSuffix(cell) : "");
|
|
103
|
+
source.insertLeft(node.start, `${prefix}\``);
|
|
104
|
+
source.insertRight(node.end, `\`${suffix}`);
|
|
105
|
+
return String(source);
|
|
100
106
|
}
|
|
101
107
|
function getRaw(cell) {
|
|
102
108
|
return cell.mode !== "md";
|
|
103
109
|
}
|
|
104
|
-
function
|
|
110
|
+
function getPrefix(cell) {
|
|
105
111
|
return cell.mode === "tex"
|
|
106
112
|
? "tex.block"
|
|
107
113
|
: cell.mode === "sql"
|
|
108
|
-
?
|
|
114
|
+
? getSqlPrefix(cell)
|
|
109
115
|
: isInterpreter(cell.mode)
|
|
110
|
-
?
|
|
116
|
+
? getInterpreterPrefix(cell)
|
|
111
117
|
: cell.mode;
|
|
112
118
|
}
|
|
113
|
-
function
|
|
119
|
+
function getSqlPrefix(cell) {
|
|
114
120
|
const { id, database = "var:db", since } = cell;
|
|
115
121
|
return database.startsWith("var:")
|
|
116
122
|
? `${database.slice("var:".length)}.sql`
|
|
117
123
|
: `DatabaseClient(${JSON.stringify(database)}, {id: ${id}${since === undefined ? "" : `, since: ${JSON.stringify(since)}`}}).sql`;
|
|
118
124
|
}
|
|
119
|
-
function
|
|
125
|
+
function getInterpreterPrefix(cell) {
|
|
120
126
|
const { id, mode, format, since } = cell;
|
|
121
127
|
return `Interpreter(${JSON.stringify(mode)}, {id: ${id}${format === undefined ? "" : `, format: ${JSON.stringify(format)}`}${since === undefined ? "" : `, since: ${JSON.stringify(since)}`}}).run(`;
|
|
122
128
|
}
|
|
@@ -129,7 +135,7 @@ function getSuffix(cell) {
|
|
|
129
135
|
}
|
|
130
136
|
function getInterpreterSuffix(cell) {
|
|
131
137
|
const method = getInterpreterMethod(cell.format);
|
|
132
|
-
return method ? `).then((file) => file${method})` : "";
|
|
138
|
+
return method ? `).then((file) => file${method})` : ")";
|
|
133
139
|
}
|
|
134
140
|
function escapeTemplateElements(source, node) {
|
|
135
141
|
for (const quasi of node.quasis) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ScriptTarget, transpile as transpileTypeScript } from "typescript";
|
|
1
2
|
import { toCell } from "../lib/notebook.js";
|
|
2
3
|
import { rewriteFileExpressions } from "./files.js";
|
|
3
4
|
import { hasImportDeclaration } from "./imports.js";
|
|
@@ -18,16 +19,18 @@ export function transpile(input, mode, options) {
|
|
|
18
19
|
cell = input;
|
|
19
20
|
input = cell.value;
|
|
20
21
|
}
|
|
21
|
-
const transpiled = mode === "
|
|
22
|
-
?
|
|
23
|
-
: mode
|
|
24
|
-
?
|
|
25
|
-
:
|
|
22
|
+
const transpiled = mode === "ts"
|
|
23
|
+
? transpileJavaScript(transpileTypeScript(input, { target: ScriptTarget.ESNext }), options)
|
|
24
|
+
: mode === "ojs"
|
|
25
|
+
? transpileObservable(input, options)
|
|
26
|
+
: mode !== "js"
|
|
27
|
+
? transpileJavaScript(transpileTemplate(cell), options)
|
|
28
|
+
: transpileJavaScript(input, options);
|
|
26
29
|
if (transpiled.output === undefined)
|
|
27
30
|
transpiled.output = cell.output;
|
|
28
31
|
if (cell.hidden)
|
|
29
32
|
transpiled.autodisplay = false;
|
|
30
|
-
else if (mode !== "js" && mode !== "ojs") {
|
|
33
|
+
else if (mode !== "js" && mode !== "ts" && mode !== "ojs") {
|
|
31
34
|
transpiled.autodisplay = !!input;
|
|
32
35
|
transpiled.autoview = mode === "sql" && transpiled.autodisplay && !!transpiled.output;
|
|
33
36
|
if (transpiled.autoview)
|
|
@@ -15,7 +15,9 @@ export function getInterpreterExtension(format) {
|
|
|
15
15
|
case "tsv":
|
|
16
16
|
case "png":
|
|
17
17
|
case "gif":
|
|
18
|
+
case "svg":
|
|
18
19
|
case "webp":
|
|
20
|
+
case "xml":
|
|
19
21
|
return `.${format}`;
|
|
20
22
|
default:
|
|
21
23
|
return ".bin";
|
|
@@ -28,6 +30,7 @@ export function getInterpreterMethod(format) {
|
|
|
28
30
|
case "json":
|
|
29
31
|
case "blob":
|
|
30
32
|
case "text":
|
|
33
|
+
case "xml":
|
|
31
34
|
return `.${format}()`;
|
|
32
35
|
case "html":
|
|
33
36
|
return `.text().then((text) => html({raw: [text]}))`;
|
|
@@ -36,6 +39,7 @@ export function getInterpreterMethod(format) {
|
|
|
36
39
|
case "jpeg":
|
|
37
40
|
case "png":
|
|
38
41
|
case "gif":
|
|
42
|
+
case "svg":
|
|
39
43
|
case "webp":
|
|
40
44
|
return ".image()";
|
|
41
45
|
case "csv":
|
|
@@ -21,7 +21,7 @@ export interface CellSpec {
|
|
|
21
21
|
/** the committed cell value; defaults to empty */
|
|
22
22
|
value?: string;
|
|
23
23
|
/** the mode; affects how the value is evaluated; defaults to js */
|
|
24
|
-
mode?: "js" | "ojs" | "md" | "html" | "tex" | "dot" | "sql" | "node" | "python";
|
|
24
|
+
mode?: "js" | "ts" | "ojs" | "md" | "html" | "tex" | "dot" | "sql" | "node" | "python";
|
|
25
25
|
/** if true, the editor will stay open when not focused; defaults to false */
|
|
26
26
|
pinned?: boolean;
|
|
27
27
|
/** if true, implicit display will be suppressed; defaults to false */
|
|
@@ -29,7 +29,7 @@ export interface CellSpec {
|
|
|
29
29
|
/** if present, exposes the cell’s value to the rest of the notebook */
|
|
30
30
|
output?: string;
|
|
31
31
|
/** for data loader cells, how the data is represented */
|
|
32
|
-
format?: "text" | "blob" | "buffer" | "json" | "csv" | "tsv" | "jpeg" | "gif" | "webp" | "png" | "arrow" | "parquet" | "html";
|
|
32
|
+
format?: "text" | "blob" | "buffer" | "json" | "csv" | "tsv" | "jpeg" | "gif" | "webp" | "png" | "arrow" | "parquet" | "html" | "svg" | "xml";
|
|
33
33
|
/** for SQL cells, the database to query; use var:<name> to refer to a variable */
|
|
34
34
|
database?: string;
|
|
35
35
|
/** for SQL cells, the oldest allowable age of the cached query result */
|
package/dist/src/lib/notebook.js
CHANGED
|
@@ -24,5 +24,5 @@ function asDate(date) {
|
|
|
24
24
|
return date instanceof Date ? date : new Date(date);
|
|
25
25
|
}
|
|
26
26
|
export function defaultPinned(mode) {
|
|
27
|
-
return mode === "js" || mode === "sql" || isInterpreter(mode) || mode === "ojs";
|
|
27
|
+
return mode === "js" || mode === "ts" || mode === "sql" || isInterpreter(mode) || mode === "ojs";
|
|
28
28
|
}
|
|
@@ -74,6 +74,8 @@ function serializeMode(mode) {
|
|
|
74
74
|
return "text/x-python";
|
|
75
75
|
case "ojs":
|
|
76
76
|
return "application/vnd.observable.javascript";
|
|
77
|
+
case "ts":
|
|
78
|
+
return "text/x-typescript";
|
|
77
79
|
default:
|
|
78
80
|
return "module";
|
|
79
81
|
}
|
|
@@ -96,6 +98,8 @@ function deserializeMode(mode) {
|
|
|
96
98
|
return "python";
|
|
97
99
|
case "application/vnd.observable.javascript":
|
|
98
100
|
return "ojs";
|
|
101
|
+
case "text/x-typescript":
|
|
102
|
+
return "ts";
|
|
99
103
|
default:
|
|
100
104
|
return "js";
|
|
101
105
|
}
|
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/observablehq/notebook-kit.git"
|
|
7
7
|
},
|
|
8
|
-
"version": "1.
|
|
8
|
+
"version": "1.4.0-rc.1",
|
|
9
9
|
"type": "module",
|
|
10
10
|
"scripts": {
|
|
11
11
|
"test": "vitest",
|
|
@@ -61,6 +61,7 @@
|
|
|
61
61
|
"jsdom": "^26.1.0",
|
|
62
62
|
"markdown-it": "^14.1.0",
|
|
63
63
|
"markdown-it-anchor": "^9.2.0",
|
|
64
|
+
"typescript": "^5.8.3",
|
|
64
65
|
"vite": "^7.0.0"
|
|
65
66
|
},
|
|
66
67
|
"devDependencies": {
|
|
@@ -77,7 +78,6 @@
|
|
|
77
78
|
"postgres": "^3.4.7",
|
|
78
79
|
"snowflake-sdk": "^2.1.3",
|
|
79
80
|
"tsx": "^4.20.3",
|
|
80
|
-
"typescript": "^5.8.3",
|
|
81
81
|
"typescript-eslint": "^8.35.0",
|
|
82
82
|
"vitest": "^3.2.4"
|
|
83
83
|
},
|