@jefuriiij/synthra 0.1.10 → 0.1.12
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/CHANGELOG.md +122 -0
- package/README.md +5 -3
- package/dist/cli/index.js +189 -66
- package/dist/cli/index.js.map +1 -1
- package/dist/server/index.js +100 -29
- package/dist/server/index.js.map +1 -1
- package/package.json +3 -2
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Synthra changelog
|
|
2
|
+
|
|
3
|
+
Notable changes per version. This file ships inside the npm tarball — `syn .`
|
|
4
|
+
reads it after an auto-update to show you what changed.
|
|
5
|
+
|
|
6
|
+
For older versions, see [GitHub Releases](https://github.com/jefuriiij/synthra/releases).
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## [0.1.12] — 2026-05-29
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **`Language.query is deprecated` spam at scan time.** Every parsed file
|
|
15
|
+
printed the warning — 57 prints on a Flutter codebase, one per parsed
|
|
16
|
+
file. Switched all four parsers (TypeScript, JavaScript, Python, Dart,
|
|
17
|
+
plus the generic helper) from the deprecated `language.query(QUERY)`
|
|
18
|
+
to `new Query(language, QUERY)`. No behavior change, just clean
|
|
19
|
+
terminal output.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## [0.1.11] — 2026-05-29
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
- **Dart parser actually runs now.** Was silently broken since v0.1 due to an
|
|
28
|
+
ABI mismatch (shipped wasm was ABI v15, pinned `web-tree-sitter` only
|
|
29
|
+
supported v13–v14). Every `.dart` file got zero symbols, zero imports —
|
|
30
|
+
the exception was swallowed by the parser's try/catch. Bumped
|
|
31
|
+
`web-tree-sitter` to `^0.25.10` to fix.
|
|
32
|
+
- **Real Dart symbol extraction.** Classes, mixins, extensions, enums,
|
|
33
|
+
typedefs, top-level functions, methods, getters, setters, constructors.
|
|
34
|
+
- **Dart import normalization.** `package:foo/bar.dart` and `dart:async` are
|
|
35
|
+
stripped (cross-project); bare `'sibling.dart'` is rewritten to
|
|
36
|
+
`./sibling.dart` so the project resolver can complete them.
|
|
37
|
+
|
|
38
|
+
### Changed
|
|
39
|
+
|
|
40
|
+
- **Update check runs on every `syn .`** (no more 24h cache). If you're on
|
|
41
|
+
latest, stays silent. If outdated, prompts `[y/N]` as before.
|
|
42
|
+
- **Auto-update now shows a changelog.** After `npm install -g …@latest`
|
|
43
|
+
succeeds, Synthra prints the new version's section from this file before
|
|
44
|
+
telling you to re-run. Catches `npm install` outside of `syn .` too —
|
|
45
|
+
next startup compares your current version to `~/.synthra/last-seen-version.json`
|
|
46
|
+
and prints if it's newer.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## [0.1.10] — 2026-05-29
|
|
51
|
+
|
|
52
|
+
### Changed
|
|
53
|
+
|
|
54
|
+
- **CLAUDE.md policy v2 → v3.** Session-end now goes through
|
|
55
|
+
`context_remember({kind: "task"|"decision"|"next"})` instead of writing
|
|
56
|
+
`.synthra/CONTEXT.md` directly. The Stop hook always re-rendered CONTEXT.md
|
|
57
|
+
from `context-store.json` — under v2 Claude's direct writes were getting
|
|
58
|
+
wiped on session end. Existing v2 blocks auto-upgrade.
|
|
59
|
+
|
|
60
|
+
### Added
|
|
61
|
+
|
|
62
|
+
- **Scanner ignores more build caches.** `.dart_tool/`, `.flutter-plugins`,
|
|
63
|
+
`.flutter-plugins-dependencies`, `.gradle/`, `target/`, `Pods/`,
|
|
64
|
+
`DerivedData/`, `__pycache__/`, `.venv/`, `venv/`, `.tox/`,
|
|
65
|
+
`.pytest_cache/`, `.mypy_cache/`, `.ruff_cache/`, `obj/`, `.vs/`.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## [0.1.9] — 2026-05-29
|
|
70
|
+
|
|
71
|
+
### Fixed
|
|
72
|
+
|
|
73
|
+
- **Crash on prototype-colliding symbol names.** `buildSymbolIndex` built
|
|
74
|
+
the lookup on a plain `{}`, so a symbol named `toString` (which every
|
|
75
|
+
Dart class overrides), `constructor`, `valueOf`, etc. resolved to the
|
|
76
|
+
inherited `Object.prototype` member and crashed on `.push`. Now uses
|
|
77
|
+
`Object.create(null)` on both fresh-build and load-from-disk paths.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## [0.1.8] — 2026-05-29
|
|
82
|
+
|
|
83
|
+
### Added
|
|
84
|
+
|
|
85
|
+
- **Interactive auto-update.** `syn .` checks npm at startup; if a newer
|
|
86
|
+
version is available, prompts `[y/N]`. On `y`, runs
|
|
87
|
+
`npm install -g @jefuriiij/synthra@latest` with stdio inherited and
|
|
88
|
+
exits with re-run instructions. Non-TTY runs (CI, piped stdin) fall
|
|
89
|
+
back to a silent one-line hint. `SYN_NO_UPDATE_CHECK=1` opts out.
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## [0.1.7] — 2026-05-29
|
|
94
|
+
|
|
95
|
+
### Fixed
|
|
96
|
+
|
|
97
|
+
- **JS parser missed CommonJS imports + JS class names.** Unified TS/JS
|
|
98
|
+
query only matched ES `import_statement`, and used `(type_identifier)`
|
|
99
|
+
for class names — which is TS-grammar-only. Result: every `.js`/`.cjs`/
|
|
100
|
+
`.mjs` file silently produced zero imports, and any class in a JS file
|
|
101
|
+
was skipped. Split into `TS_QUERY` and `JS_QUERY`; JS query adds a
|
|
102
|
+
`require()` capture and uses `(identifier)` for class names.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## [0.1.6] — 2026-05-29
|
|
107
|
+
|
|
108
|
+
### Fixed
|
|
109
|
+
|
|
110
|
+
- **MCP registration now uses `--scope project`** so the Claude Code IDE
|
|
111
|
+
extension actually sees Synthra. The previous `--scope local` wrote to
|
|
112
|
+
a per-project section of `~/.claude.json` that only the `claude` CLI
|
|
113
|
+
reads — invisible to the IDE.
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## [0.1.5] and earlier
|
|
118
|
+
|
|
119
|
+
See [GitHub commits](https://github.com/jefuriiij/synthra/commits/main) for
|
|
120
|
+
detail. v0.1.5 introduced the v2 policy template with namespace + skip rules;
|
|
121
|
+
v0.1.4 fixed a DEP0190 deprecation on Windows; v0.1.3 was the dashboard
|
|
122
|
+
redesign (Cool Marine palette, FAQ modal, savings audit row).
|
package/README.md
CHANGED
|
@@ -177,13 +177,15 @@ Claude Code honors the block and pivots to the MCP tool. The structured pack is
|
|
|
177
177
|
|
|
178
178
|
## Self-update
|
|
179
179
|
|
|
180
|
-
|
|
180
|
+
Every `syn .` checks the npm registry for a newer version (no cache — always fresh; 2s hard timeout; silent fallthrough on network failure). When you're on latest, the check stays silent and `syn .` proceeds. When you're outdated:
|
|
181
181
|
|
|
182
182
|
- **Interactive shell** (TTY) → you see `[syn] Synthra X.Y.Z is available (you have A.B.C). Update now? [y/N]:` *before* the scan starts. Type `y` to install; press Enter (or anything else) to skip and continue with the current version.
|
|
183
|
-
- **Non-interactive** (CI, piped stdin) → silent one-line
|
|
183
|
+
- **Non-interactive** (CI, piped stdin) → silent one-line hint, no prompt.
|
|
184
184
|
- **Disabled entirely** → set `SYN_NO_UPDATE_CHECK=1`.
|
|
185
185
|
|
|
186
|
-
On `y`, Synthra spawns `npm install -g @jefuriiij/synthra@latest` with stdio inherited (you see npm's progress), then exits with re-run instructions — the running Node process is still the old version and can't hot-swap its own code mid-run.
|
|
186
|
+
On `y`, Synthra spawns `npm install -g @jefuriiij/synthra@latest` with stdio inherited (you see npm's progress), then **prints the new version's `CHANGELOG.md` section** (so you know what you just got), then exits with re-run instructions — the running Node process is still the old version and can't hot-swap its own code mid-run.
|
|
187
|
+
|
|
188
|
+
If you upgrade via `npm install -g @jefuriiij/synthra@latest` directly (outside Synthra's prompt), the next `syn .` notices and prints the changelog anyway. It tracks the last-seen version at `~/.synthra/last-seen-version.json`.
|
|
187
189
|
|
|
188
190
|
---
|
|
189
191
|
|
package/dist/cli/index.js
CHANGED
|
@@ -18,7 +18,7 @@ var init_package = __esm({
|
|
|
18
18
|
"package.json"() {
|
|
19
19
|
package_default = {
|
|
20
20
|
name: "@jefuriiij/synthra",
|
|
21
|
-
version: "0.1.
|
|
21
|
+
version: "0.1.12",
|
|
22
22
|
publishConfig: {
|
|
23
23
|
access: "public"
|
|
24
24
|
},
|
|
@@ -39,6 +39,7 @@ var init_package = __esm({
|
|
|
39
39
|
"dist",
|
|
40
40
|
"bin",
|
|
41
41
|
"README.md",
|
|
42
|
+
"CHANGELOG.md",
|
|
42
43
|
"LICENSE",
|
|
43
44
|
"ROADMAP.md"
|
|
44
45
|
],
|
|
@@ -71,7 +72,7 @@ var init_package = __esm({
|
|
|
71
72
|
ignore: "^7.0.0",
|
|
72
73
|
sade: "^1.8.1",
|
|
73
74
|
"tree-sitter-wasms": "^0.1.12",
|
|
74
|
-
"web-tree-sitter": "
|
|
75
|
+
"web-tree-sitter": "^0.25.10"
|
|
75
76
|
},
|
|
76
77
|
devDependencies: {
|
|
77
78
|
"@types/cross-spawn": "^6.0.6",
|
|
@@ -2103,7 +2104,7 @@ function extractKeywords(content, _ext) {
|
|
|
2103
2104
|
}
|
|
2104
2105
|
|
|
2105
2106
|
// src/scanner/extract.ts
|
|
2106
|
-
var RESOLVE_EXTS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py", ".svelte", ".vue"];
|
|
2107
|
+
var RESOLVE_EXTS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py", ".svelte", ".vue", ".dart"];
|
|
2107
2108
|
var INDEX_FILES = ["index.ts", "index.tsx", "index.js", "index.jsx", "__init__.py"];
|
|
2108
2109
|
function fileId(relPath) {
|
|
2109
2110
|
return `file:${relPath}`;
|
|
@@ -2243,9 +2244,10 @@ function buildSymbolIndex(graph) {
|
|
|
2243
2244
|
// src/scanner/parser.ts
|
|
2244
2245
|
import { readFile as readFile6 } from "fs/promises";
|
|
2245
2246
|
import { createRequire } from "module";
|
|
2246
|
-
import Parser from "web-tree-sitter";
|
|
2247
|
+
import { Language, Parser } from "web-tree-sitter";
|
|
2247
2248
|
|
|
2248
2249
|
// src/scanner/parsers/_generic.ts
|
|
2250
|
+
import { Query } from "web-tree-sitter";
|
|
2249
2251
|
function firstLine(text, max = 200) {
|
|
2250
2252
|
const line = text.split(/\r?\n/, 1)[0] ?? "";
|
|
2251
2253
|
return line.length > max ? line.slice(0, max) + "\u2026" : line;
|
|
@@ -2260,7 +2262,7 @@ async function runGenericParser(config, f, source) {
|
|
|
2260
2262
|
const { parser, language } = await createParser(config.grammar);
|
|
2261
2263
|
const tree = parser.parse(source);
|
|
2262
2264
|
if (!tree) return { file: f, source, symbols, imports, calls: [] };
|
|
2263
|
-
const query = language
|
|
2265
|
+
const query = new Query(language, config.query);
|
|
2264
2266
|
const matches = query.matches(tree.rootNode);
|
|
2265
2267
|
for (const match of matches) {
|
|
2266
2268
|
const byName = /* @__PURE__ */ new Map();
|
|
@@ -2391,27 +2393,95 @@ async function parseCSharp(f, source) {
|
|
|
2391
2393
|
}
|
|
2392
2394
|
|
|
2393
2395
|
// src/scanner/parsers/dart.ts
|
|
2396
|
+
import { Query as Query2 } from "web-tree-sitter";
|
|
2394
2397
|
var QUERY4 = `
|
|
2395
|
-
(class_definition (identifier) @class.name) @class
|
|
2396
|
-
(mixin_declaration (identifier) @
|
|
2397
|
-
(extension_declaration (identifier) @
|
|
2398
|
-
(
|
|
2398
|
+
(class_definition name: (identifier) @class.name) @class
|
|
2399
|
+
(mixin_declaration (identifier) @mixin.name) @mixin
|
|
2400
|
+
(extension_declaration name: (identifier) @ext.name) @ext
|
|
2401
|
+
(enum_declaration name: (identifier) @enum.name) @enum
|
|
2402
|
+
(type_alias (type_identifier) @typedef.name) @typedef
|
|
2403
|
+
|
|
2404
|
+
(program (function_signature name: (identifier) @function.name) @function)
|
|
2405
|
+
|
|
2406
|
+
(method_signature (function_signature name: (identifier) @method.name)) @method
|
|
2407
|
+
(method_signature (getter_signature name: (identifier) @getter.name)) @getter
|
|
2408
|
+
(method_signature (setter_signature name: (identifier) @setter.name)) @setter
|
|
2409
|
+
(constructor_signature name: (identifier) @ctor.name) @ctor
|
|
2410
|
+
|
|
2411
|
+
(import_or_export (library_import (import_specification (configurable_uri (uri (string_literal) @import)))))
|
|
2399
2412
|
`;
|
|
2413
|
+
var DECLS = [
|
|
2414
|
+
{ declCap: "class", nameCap: "class.name", kind: "class" },
|
|
2415
|
+
{ declCap: "mixin", nameCap: "mixin.name", kind: "class" },
|
|
2416
|
+
{ declCap: "ext", nameCap: "ext.name", kind: "class" },
|
|
2417
|
+
{ declCap: "enum", nameCap: "enum.name", kind: "enum" },
|
|
2418
|
+
{ declCap: "typedef", nameCap: "typedef.name", kind: "type" },
|
|
2419
|
+
{ declCap: "function", nameCap: "function.name", kind: "function" },
|
|
2420
|
+
{ declCap: "method", nameCap: "method.name", kind: "method" },
|
|
2421
|
+
{ declCap: "getter", nameCap: "getter.name", kind: "method" },
|
|
2422
|
+
{ declCap: "setter", nameCap: "setter.name", kind: "method" },
|
|
2423
|
+
{ declCap: "ctor", nameCap: "ctor.name", kind: "method" }
|
|
2424
|
+
];
|
|
2425
|
+
function firstLine2(text, max = 200) {
|
|
2426
|
+
const line = text.split(/\r?\n/, 1)[0] ?? "";
|
|
2427
|
+
return line.length > max ? line.slice(0, max) + "\u2026" : line;
|
|
2428
|
+
}
|
|
2429
|
+
function normalizeDartImport(raw) {
|
|
2430
|
+
const stripped = raw.replace(/^['"]|['"]$/g, "");
|
|
2431
|
+
if (!stripped) return null;
|
|
2432
|
+
if (stripped.startsWith("package:")) return null;
|
|
2433
|
+
if (stripped.startsWith("dart:")) return null;
|
|
2434
|
+
if (stripped.startsWith(".") || stripped.startsWith("/")) return stripped;
|
|
2435
|
+
return `./${stripped}`;
|
|
2436
|
+
}
|
|
2400
2437
|
async function parseDart(f, source) {
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2438
|
+
let symbols = [];
|
|
2439
|
+
let imports = [];
|
|
2440
|
+
try {
|
|
2441
|
+
const { parser, language } = await createParser("dart");
|
|
2442
|
+
const tree = parser.parse(source);
|
|
2443
|
+
if (!tree) return { file: f, source, symbols, imports, calls: [] };
|
|
2444
|
+
const query = new Query2(language, QUERY4);
|
|
2445
|
+
const matches = query.matches(tree.rootNode);
|
|
2446
|
+
for (const match of matches) {
|
|
2447
|
+
const byName = /* @__PURE__ */ new Map();
|
|
2448
|
+
for (const cap of match.captures) byName.set(cap.name, cap.node);
|
|
2449
|
+
let matched = null;
|
|
2450
|
+
for (const d of DECLS) {
|
|
2451
|
+
if (byName.has(d.declCap) && byName.has(d.nameCap)) {
|
|
2452
|
+
matched = d;
|
|
2453
|
+
break;
|
|
2454
|
+
}
|
|
2455
|
+
}
|
|
2456
|
+
if (matched) {
|
|
2457
|
+
const declNode = byName.get(matched.declCap);
|
|
2458
|
+
const nameNode = byName.get(matched.nameCap);
|
|
2459
|
+
symbols.push({
|
|
2460
|
+
name: nameNode.text,
|
|
2461
|
+
kind: matched.kind,
|
|
2462
|
+
startLine: declNode.startPosition.row + 1,
|
|
2463
|
+
endLine: declNode.endPosition.row + 1,
|
|
2464
|
+
signature: firstLine2(declNode.text)
|
|
2465
|
+
});
|
|
2466
|
+
continue;
|
|
2467
|
+
}
|
|
2468
|
+
const importNode = byName.get("import");
|
|
2469
|
+
if (importNode) {
|
|
2470
|
+
const norm = normalizeDartImport(importNode.text);
|
|
2471
|
+
if (norm) imports.push(norm);
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2474
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2475
|
+
symbols = symbols.filter((s) => {
|
|
2476
|
+
const k = `${s.name}:${s.startLine}`;
|
|
2477
|
+
if (seen.has(k)) return false;
|
|
2478
|
+
seen.add(k);
|
|
2479
|
+
return true;
|
|
2480
|
+
});
|
|
2481
|
+
imports = Array.from(new Set(imports));
|
|
2482
|
+
} catch {
|
|
2483
|
+
}
|
|
2484
|
+
return { file: f, source, symbols, imports, calls: [] };
|
|
2415
2485
|
}
|
|
2416
2486
|
|
|
2417
2487
|
// src/scanner/parsers/go.ts
|
|
@@ -2515,6 +2585,7 @@ async function parsePhp(f, source) {
|
|
|
2515
2585
|
}
|
|
2516
2586
|
|
|
2517
2587
|
// src/scanner/parsers/python.ts
|
|
2588
|
+
import { Query as Query3 } from "web-tree-sitter";
|
|
2518
2589
|
var QUERY9 = `
|
|
2519
2590
|
(function_definition name: (identifier) @function.name) @function
|
|
2520
2591
|
(class_definition name: (identifier) @class.name) @class
|
|
@@ -2522,7 +2593,7 @@ var QUERY9 = `
|
|
|
2522
2593
|
(import_from_statement module_name: (dotted_name) @import.from)
|
|
2523
2594
|
(import_from_statement module_name: (relative_import) @import.from)
|
|
2524
2595
|
`;
|
|
2525
|
-
function
|
|
2596
|
+
function firstLine3(text, max = 200) {
|
|
2526
2597
|
const line = text.split(/\r?\n/, 1)[0] ?? "";
|
|
2527
2598
|
return line.length > max ? line.slice(0, max) + "\u2026" : line;
|
|
2528
2599
|
}
|
|
@@ -2533,7 +2604,7 @@ async function parsePython(f, source) {
|
|
|
2533
2604
|
const { parser, language } = await createParser("python");
|
|
2534
2605
|
const tree = parser.parse(source);
|
|
2535
2606
|
if (!tree) return { file: f, source, symbols, imports, calls: [] };
|
|
2536
|
-
const query = language
|
|
2607
|
+
const query = new Query3(language, QUERY9);
|
|
2537
2608
|
const matches = query.matches(tree.rootNode);
|
|
2538
2609
|
for (const match of matches) {
|
|
2539
2610
|
const byName = /* @__PURE__ */ new Map();
|
|
@@ -2548,7 +2619,7 @@ async function parsePython(f, source) {
|
|
|
2548
2619
|
kind: isMethod ? "method" : "function",
|
|
2549
2620
|
startLine: funcDecl.startPosition.row + 1,
|
|
2550
2621
|
endLine: funcDecl.endPosition.row + 1,
|
|
2551
|
-
signature:
|
|
2622
|
+
signature: firstLine3(funcDecl.text)
|
|
2552
2623
|
});
|
|
2553
2624
|
continue;
|
|
2554
2625
|
}
|
|
@@ -2560,7 +2631,7 @@ async function parsePython(f, source) {
|
|
|
2560
2631
|
kind: "class",
|
|
2561
2632
|
startLine: classDecl.startPosition.row + 1,
|
|
2562
2633
|
endLine: classDecl.endPosition.row + 1,
|
|
2563
|
-
signature:
|
|
2634
|
+
signature: firstLine3(classDecl.text)
|
|
2564
2635
|
});
|
|
2565
2636
|
continue;
|
|
2566
2637
|
}
|
|
@@ -2631,6 +2702,7 @@ async function parseRust(f, source) {
|
|
|
2631
2702
|
}
|
|
2632
2703
|
|
|
2633
2704
|
// src/scanner/parsers/typescript.ts
|
|
2705
|
+
import { Query as Query4 } from "web-tree-sitter";
|
|
2634
2706
|
var TS_QUERY = `
|
|
2635
2707
|
(function_declaration name: (identifier) @function.name) @function
|
|
2636
2708
|
(class_declaration name: (type_identifier) @class.name) @class
|
|
@@ -2660,7 +2732,7 @@ function queryFor(grammar) {
|
|
|
2660
2732
|
function unquote(s) {
|
|
2661
2733
|
return s.replace(/^["'`]|["'`]$/g, "");
|
|
2662
2734
|
}
|
|
2663
|
-
function
|
|
2735
|
+
function firstLine4(text, max = 200) {
|
|
2664
2736
|
const line = text.split(/\r?\n/, 1)[0] ?? "";
|
|
2665
2737
|
return line.length > max ? line.slice(0, max) + "\u2026" : line;
|
|
2666
2738
|
}
|
|
@@ -2680,7 +2752,7 @@ async function parseTypeScript(f, source) {
|
|
|
2680
2752
|
const { parser, language } = await createParser(grammar);
|
|
2681
2753
|
const tree = parser.parse(source);
|
|
2682
2754
|
if (!tree) return { file: f, source, symbols, imports, calls: [] };
|
|
2683
|
-
const query = language
|
|
2755
|
+
const query = new Query4(language, queryFor(grammar));
|
|
2684
2756
|
const matches = query.matches(tree.rootNode);
|
|
2685
2757
|
for (const match of matches) {
|
|
2686
2758
|
const byName = /* @__PURE__ */ new Map();
|
|
@@ -2692,7 +2764,7 @@ async function parseTypeScript(f, source) {
|
|
|
2692
2764
|
kind: shape.kind,
|
|
2693
2765
|
startLine: shape.decl.startPosition.row + 1,
|
|
2694
2766
|
endLine: shape.decl.endPosition.row + 1,
|
|
2695
|
-
signature:
|
|
2767
|
+
signature: firstLine4(shape.decl.text)
|
|
2696
2768
|
});
|
|
2697
2769
|
continue;
|
|
2698
2770
|
}
|
|
@@ -2837,7 +2909,7 @@ async function loadGrammar(name) {
|
|
|
2837
2909
|
const cached = languageCache.get(name);
|
|
2838
2910
|
if (cached) return cached;
|
|
2839
2911
|
const wasmPath = require2.resolve(GRAMMAR_FILES[name]);
|
|
2840
|
-
const lang = await
|
|
2912
|
+
const lang = await Language.load(wasmPath);
|
|
2841
2913
|
languageCache.set(name, lang);
|
|
2842
2914
|
return lang;
|
|
2843
2915
|
}
|
|
@@ -4712,9 +4784,8 @@ import { join as join10 } from "path";
|
|
|
4712
4784
|
import { createInterface } from "readline/promises";
|
|
4713
4785
|
import spawn from "cross-spawn";
|
|
4714
4786
|
var PKG_NAME = "@jefuriiij/synthra";
|
|
4715
|
-
var
|
|
4716
|
-
var
|
|
4717
|
-
var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
4787
|
+
var SYNTHRA_DIR = join10(homedir3(), ".synthra");
|
|
4788
|
+
var LAST_SEEN_PATH = join10(SYNTHRA_DIR, "last-seen-version.json");
|
|
4718
4789
|
var REGISTRY_URL = `https://registry.npmjs.org/${encodeURIComponent(PKG_NAME)}/latest`;
|
|
4719
4790
|
var FETCH_TIMEOUT_MS = 2e3;
|
|
4720
4791
|
var currentVersionCache = null;
|
|
@@ -4729,23 +4800,6 @@ async function getCurrentVersion() {
|
|
|
4729
4800
|
return "0.0.0";
|
|
4730
4801
|
}
|
|
4731
4802
|
}
|
|
4732
|
-
async function readCache() {
|
|
4733
|
-
try {
|
|
4734
|
-
const raw = await readFile14(CACHE_PATH, "utf8");
|
|
4735
|
-
const parsed = JSON.parse(raw);
|
|
4736
|
-
if (!parsed.checked_at || !parsed.latest_version) return null;
|
|
4737
|
-
return parsed;
|
|
4738
|
-
} catch {
|
|
4739
|
-
return null;
|
|
4740
|
-
}
|
|
4741
|
-
}
|
|
4742
|
-
async function writeCache(cache) {
|
|
4743
|
-
try {
|
|
4744
|
-
await mkdir10(CACHE_DIR, { recursive: true });
|
|
4745
|
-
await writeFile9(CACHE_PATH, JSON.stringify(cache, null, 2), "utf8");
|
|
4746
|
-
} catch {
|
|
4747
|
-
}
|
|
4748
|
-
}
|
|
4749
4803
|
function isNewer(candidate, baseline) {
|
|
4750
4804
|
const a = candidate.split(/[.-]/).map((p) => Number(p));
|
|
4751
4805
|
const b = baseline.split(/[.-]/).map((p) => Number(p));
|
|
@@ -4776,23 +4830,88 @@ async function checkForUpdate() {
|
|
|
4776
4830
|
if (process.env.SYN_NO_UPDATE_CHECK === "1") {
|
|
4777
4831
|
return { current, latest: null, hasUpdate: false };
|
|
4778
4832
|
}
|
|
4779
|
-
const
|
|
4780
|
-
const now = Date.now();
|
|
4781
|
-
const cacheAge = cache ? now - Date.parse(cache.checked_at) : Infinity;
|
|
4782
|
-
let latest = null;
|
|
4783
|
-
if (cache && cacheAge < CACHE_TTL_MS) {
|
|
4784
|
-
latest = cache.latest_version;
|
|
4785
|
-
} else {
|
|
4786
|
-
latest = await fetchLatestFromRegistry();
|
|
4787
|
-
if (latest) {
|
|
4788
|
-
await writeCache({ checked_at: (/* @__PURE__ */ new Date()).toISOString(), latest_version: latest });
|
|
4789
|
-
} else if (cache) {
|
|
4790
|
-
latest = cache.latest_version;
|
|
4791
|
-
}
|
|
4792
|
-
}
|
|
4833
|
+
const latest = await fetchLatestFromRegistry();
|
|
4793
4834
|
const hasUpdate = latest ? isNewer(latest, current) : false;
|
|
4794
4835
|
return { current, latest, hasUpdate };
|
|
4795
4836
|
}
|
|
4837
|
+
async function readLastSeen() {
|
|
4838
|
+
try {
|
|
4839
|
+
const raw = await readFile14(LAST_SEEN_PATH, "utf8");
|
|
4840
|
+
const parsed = JSON.parse(raw);
|
|
4841
|
+
return parsed.version ?? null;
|
|
4842
|
+
} catch {
|
|
4843
|
+
return null;
|
|
4844
|
+
}
|
|
4845
|
+
}
|
|
4846
|
+
async function writeLastSeen(version) {
|
|
4847
|
+
try {
|
|
4848
|
+
await mkdir10(SYNTHRA_DIR, { recursive: true });
|
|
4849
|
+
const data = { version, updated_at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
4850
|
+
await writeFile9(LAST_SEEN_PATH, JSON.stringify(data, null, 2), "utf8");
|
|
4851
|
+
} catch {
|
|
4852
|
+
}
|
|
4853
|
+
}
|
|
4854
|
+
function npmGlobalRoot() {
|
|
4855
|
+
return new Promise((resolve5) => {
|
|
4856
|
+
const chunks = [];
|
|
4857
|
+
const proc = spawn("npm", ["root", "-g"], { stdio: ["ignore", "pipe", "ignore"] });
|
|
4858
|
+
proc.stdout?.on("data", (c) => chunks.push(c));
|
|
4859
|
+
proc.on("error", () => resolve5(null));
|
|
4860
|
+
proc.on("exit", (code) => {
|
|
4861
|
+
if (code !== 0) return resolve5(null);
|
|
4862
|
+
const out = Buffer.concat(chunks).toString("utf8").trim();
|
|
4863
|
+
resolve5(out || null);
|
|
4864
|
+
});
|
|
4865
|
+
});
|
|
4866
|
+
}
|
|
4867
|
+
function extractChangelogSection(text, version) {
|
|
4868
|
+
const escapedVersion = version.replace(/\./g, "\\.");
|
|
4869
|
+
const headingRe = new RegExp(`^##\\s+\\[?v?${escapedVersion}\\]?.*$`, "m");
|
|
4870
|
+
const m = headingRe.exec(text);
|
|
4871
|
+
if (!m) return null;
|
|
4872
|
+
const startBody = m.index + m[0].length;
|
|
4873
|
+
const rest = text.slice(startBody);
|
|
4874
|
+
const nextHeadingIdx = rest.search(/^##\s+/m);
|
|
4875
|
+
const body = nextHeadingIdx < 0 ? rest : rest.slice(0, nextHeadingIdx);
|
|
4876
|
+
return body.replace(/^---\s*$/gm, "").trim() || null;
|
|
4877
|
+
}
|
|
4878
|
+
async function readInstalledChangelog() {
|
|
4879
|
+
const root = await npmGlobalRoot();
|
|
4880
|
+
if (!root) return null;
|
|
4881
|
+
try {
|
|
4882
|
+
return await readFile14(join10(root, "@jefuriiij", "synthra", "CHANGELOG.md"), "utf8");
|
|
4883
|
+
} catch {
|
|
4884
|
+
return null;
|
|
4885
|
+
}
|
|
4886
|
+
}
|
|
4887
|
+
async function printChangelogForVersion(version) {
|
|
4888
|
+
const md = await readInstalledChangelog();
|
|
4889
|
+
if (!md) return;
|
|
4890
|
+
const section = extractChangelogSection(md, version);
|
|
4891
|
+
if (!section) return;
|
|
4892
|
+
log.info("");
|
|
4893
|
+
log.info(`What's new in ${version}:`);
|
|
4894
|
+
log.info("");
|
|
4895
|
+
for (const line of section.split(/\r?\n/)) {
|
|
4896
|
+
log.info(` ${line}`);
|
|
4897
|
+
}
|
|
4898
|
+
log.info("");
|
|
4899
|
+
}
|
|
4900
|
+
async function runStartupChangelogCheck() {
|
|
4901
|
+
try {
|
|
4902
|
+
const current = await getCurrentVersion();
|
|
4903
|
+
const lastSeen = await readLastSeen();
|
|
4904
|
+
if (!lastSeen) {
|
|
4905
|
+
await writeLastSeen(current);
|
|
4906
|
+
return;
|
|
4907
|
+
}
|
|
4908
|
+
if (isNewer(current, lastSeen)) {
|
|
4909
|
+
await printChangelogForVersion(current);
|
|
4910
|
+
await writeLastSeen(current);
|
|
4911
|
+
}
|
|
4912
|
+
} catch {
|
|
4913
|
+
}
|
|
4914
|
+
}
|
|
4796
4915
|
async function promptYesNo(question) {
|
|
4797
4916
|
if (!process.stdin.isTTY) return false;
|
|
4798
4917
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
@@ -4834,7 +4953,10 @@ async function promptForUpdateOrLog() {
|
|
|
4834
4953
|
log.warn("npm install failed \u2014 continuing with current version.");
|
|
4835
4954
|
return;
|
|
4836
4955
|
}
|
|
4837
|
-
log.info(`\u2713 Updated to ${r.latest}
|
|
4956
|
+
log.info(`\u2713 Updated to ${r.latest}.`);
|
|
4957
|
+
await printChangelogForVersion(r.latest);
|
|
4958
|
+
await writeLastSeen(r.latest);
|
|
4959
|
+
log.info(`Please re-run: syn .`);
|
|
4838
4960
|
process.exit(0);
|
|
4839
4961
|
} catch {
|
|
4840
4962
|
}
|
|
@@ -4955,6 +5077,7 @@ async function defaultFlow(rawPath, opts) {
|
|
|
4955
5077
|
const projectRoot = resolve4(rawPath);
|
|
4956
5078
|
const paths = resolvePaths(projectRoot);
|
|
4957
5079
|
const cfg = loadConfig();
|
|
5080
|
+
await runStartupChangelogCheck();
|
|
4958
5081
|
await promptForUpdateOrLog();
|
|
4959
5082
|
await recordProject(projectRoot);
|
|
4960
5083
|
const scan = await scanCommand(rawPath);
|