@miclivs/cadcli 0.1.0 → 0.1.2
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/README.md +1 -1
- package/dist/core/search.d.ts +3 -0
- package/dist/core/search.js +58 -68
- package/dist/core/sqlite-search.d.ts +3 -0
- package/dist/core/sqlite-search.js +218 -0
- package/package.json +6 -2
- package/scripts/apply-acad-ts-patch.mjs +41 -0
package/README.md
CHANGED
|
@@ -111,7 +111,7 @@ The public SDK is intentionally small: `Dwg` plus stable CAD/result types. Nativ
|
|
|
111
111
|
|
|
112
112
|
## Cache
|
|
113
113
|
|
|
114
|
-
Search indexes are cached automatically in the platform-standard cache directory
|
|
114
|
+
Search indexes are cached automatically in the platform-standard cache directory. On Node.js installs, `cadcli search` uses a disk-backed SQLite FTS index when the optional SQLite dependency is available; otherwise it falls back to a low-memory scan.
|
|
115
115
|
|
|
116
116
|
```txt
|
|
117
117
|
macOS ~/Library/Caches/cadcli
|
package/dist/core/search.d.ts
CHANGED
|
@@ -15,4 +15,7 @@ export interface DwgSearchResult {
|
|
|
15
15
|
matches: string[];
|
|
16
16
|
entity: DwgEntity;
|
|
17
17
|
}
|
|
18
|
+
export declare function stringifySearchValue(value: unknown): string;
|
|
19
|
+
export declare function entitySearchFields(entity: DwgEntity): string[];
|
|
20
|
+
export declare function searchMatchesFor(entity: DwgEntity, query: string | undefined): string[];
|
|
18
21
|
export declare function searchDrawing(file: string, opts?: DwgSearchOptions, loadOpts?: LoadOptions): Promise<DwgSearchResult[]>;
|
package/dist/core/search.js
CHANGED
|
@@ -1,48 +1,23 @@
|
|
|
1
|
-
import MiniSearch from "minisearch";
|
|
2
1
|
import { loadDrawing } from "./drawing.js";
|
|
3
2
|
import { computeDrawingFingerprint, loadSearchCache, saveSearchCache, } from "./search-cache.js";
|
|
4
|
-
const SEARCH_FIELDS = ["entityId", "type", "layer", "text"];
|
|
5
|
-
const STORE_FIELDS = ["entityId", "type", "layer", "text"];
|
|
6
3
|
const DEFAULT_LIMIT = 30;
|
|
7
4
|
const MAX_MATCHES = 5;
|
|
8
|
-
function
|
|
9
|
-
return new MiniSearch({
|
|
10
|
-
fields: [...SEARCH_FIELDS],
|
|
11
|
-
storeFields: [...STORE_FIELDS],
|
|
12
|
-
searchOptions: {
|
|
13
|
-
boost: { entityId: 3, type: 2, layer: 2, text: 1 },
|
|
14
|
-
fuzzy: 0.2,
|
|
15
|
-
prefix: true,
|
|
16
|
-
},
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
function loadSearchIndex(indexJson) {
|
|
20
|
-
return MiniSearch.loadJSON(indexJson, {
|
|
21
|
-
fields: [...SEARCH_FIELDS],
|
|
22
|
-
storeFields: [...STORE_FIELDS],
|
|
23
|
-
searchOptions: {
|
|
24
|
-
boost: { entityId: 3, type: 2, layer: 2, text: 1 },
|
|
25
|
-
fuzzy: 0.2,
|
|
26
|
-
prefix: true,
|
|
27
|
-
},
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
function stringifyValue(value) {
|
|
5
|
+
export function stringifySearchValue(value) {
|
|
31
6
|
if (value === null || value === undefined)
|
|
32
7
|
return "";
|
|
33
8
|
if (["string", "number", "boolean", "bigint"].includes(typeof value)) {
|
|
34
9
|
return String(value);
|
|
35
10
|
}
|
|
36
11
|
if (Array.isArray(value))
|
|
37
|
-
return value.map(
|
|
12
|
+
return value.map(stringifySearchValue).join(" ");
|
|
38
13
|
if (typeof value === "object") {
|
|
39
14
|
return Object.entries(value)
|
|
40
|
-
.map(([key, val]) => `${key} ${
|
|
15
|
+
.map(([key, val]) => `${key} ${stringifySearchValue(val)}`)
|
|
41
16
|
.join(" ");
|
|
42
17
|
}
|
|
43
18
|
return "";
|
|
44
19
|
}
|
|
45
|
-
function
|
|
20
|
+
export function entitySearchFields(entity) {
|
|
46
21
|
const values = [
|
|
47
22
|
entity.id,
|
|
48
23
|
entity.type,
|
|
@@ -52,24 +27,28 @@ function textFields(entity) {
|
|
|
52
27
|
String(entity.data.name ?? ""),
|
|
53
28
|
String(entity.data.blockName ?? ""),
|
|
54
29
|
String(entity.data.block_name ?? ""),
|
|
55
|
-
|
|
30
|
+
stringifySearchValue(entity.data),
|
|
56
31
|
];
|
|
57
32
|
return values.filter((value) => value.trim().length > 0);
|
|
58
33
|
}
|
|
59
|
-
function
|
|
34
|
+
function buildScanDocs(doc) {
|
|
60
35
|
return doc.entities.map((entity, id) => ({
|
|
61
36
|
id,
|
|
62
37
|
entityId: entity.id,
|
|
63
38
|
type: entity.type,
|
|
64
39
|
layer: entity.layer,
|
|
65
|
-
text:
|
|
40
|
+
text: entitySearchFields(entity).join(" "),
|
|
66
41
|
entity,
|
|
67
42
|
}));
|
|
68
43
|
}
|
|
69
|
-
function
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
44
|
+
async function loadScanCorpus(file, opts, loadOpts) {
|
|
45
|
+
const fingerprint = computeDrawingFingerprint(file);
|
|
46
|
+
const cached = loadSearchCache(file, fingerprint, opts.cacheDir);
|
|
47
|
+
if (cached)
|
|
48
|
+
return cached.docs;
|
|
49
|
+
const docs = buildScanDocs(await loadDrawing(file, loadOpts));
|
|
50
|
+
saveSearchCache(file, { fingerprint, index: "scan", docs }, opts.cacheDir);
|
|
51
|
+
return docs;
|
|
73
52
|
}
|
|
74
53
|
function matchesFilter(doc, opts) {
|
|
75
54
|
if (opts.type && doc.type.toLowerCase() !== opts.type.toLowerCase()) {
|
|
@@ -80,58 +59,69 @@ function matchesFilter(doc, opts) {
|
|
|
80
59
|
}
|
|
81
60
|
return true;
|
|
82
61
|
}
|
|
83
|
-
function
|
|
62
|
+
export function searchMatchesFor(entity, query) {
|
|
84
63
|
if (!query)
|
|
85
64
|
return [];
|
|
86
65
|
const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
87
66
|
if (terms.length === 0)
|
|
88
67
|
return [];
|
|
89
|
-
return
|
|
68
|
+
return entitySearchFields(entity)
|
|
90
69
|
.filter((chunk) => {
|
|
91
70
|
const lower = chunk.toLowerCase();
|
|
92
71
|
return terms.some((term) => lower.includes(term));
|
|
93
72
|
})
|
|
94
73
|
.slice(0, MAX_MATCHES);
|
|
95
74
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
75
|
+
function queryTerms(query) {
|
|
76
|
+
return query?.toLowerCase().split(/\s+/).filter(Boolean) ?? [];
|
|
77
|
+
}
|
|
78
|
+
function scanScore(doc, terms) {
|
|
79
|
+
if (terms.length === 0)
|
|
80
|
+
return 1;
|
|
81
|
+
const entityId = doc.entityId.toLowerCase();
|
|
82
|
+
const type = doc.type.toLowerCase();
|
|
83
|
+
const layer = doc.layer?.toLowerCase() ?? "";
|
|
84
|
+
const text = doc.text.toLowerCase();
|
|
85
|
+
let score = 0;
|
|
86
|
+
for (const term of terms) {
|
|
87
|
+
if (entityId.includes(term))
|
|
88
|
+
score += 3;
|
|
89
|
+
if (type.includes(term))
|
|
90
|
+
score += 2;
|
|
91
|
+
if (layer.includes(term))
|
|
92
|
+
score += 2;
|
|
93
|
+
if (text.includes(term))
|
|
94
|
+
score += 1;
|
|
104
95
|
}
|
|
105
|
-
|
|
106
|
-
const index = buildIndex(docs);
|
|
107
|
-
saveSearchCache(file, { fingerprint, index: JSON.stringify(index), docs }, opts.cacheDir);
|
|
108
|
-
return { docs, index };
|
|
96
|
+
return score;
|
|
109
97
|
}
|
|
110
|
-
function resultScore(
|
|
111
|
-
return Math.round((
|
|
98
|
+
function resultScore(score) {
|
|
99
|
+
return Math.round((score || 1) * 10) / 10;
|
|
112
100
|
}
|
|
113
|
-
|
|
114
|
-
const
|
|
101
|
+
async function searchWithScan(file, opts = {}, loadOpts = {}) {
|
|
102
|
+
const docs = await loadScanCorpus(file, opts, loadOpts);
|
|
115
103
|
const query = opts.query?.trim();
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
})
|
|
122
|
-
: docs.map((doc) => ({ id: doc.id, score: 1 }));
|
|
123
|
-
return raw
|
|
124
|
-
.map((result) => ({ result, doc: byId.get(result.id) }))
|
|
125
|
-
.filter((item) => Boolean(item.doc))
|
|
126
|
-
.filter(({ doc }) => matchesFilter(doc, opts))
|
|
127
|
-
.map(({ result, doc }) => ({
|
|
104
|
+
const terms = queryTerms(query);
|
|
105
|
+
return docs
|
|
106
|
+
.filter((doc) => matchesFilter(doc, opts))
|
|
107
|
+
.map((doc) => ({ doc, score: scanScore(doc, terms) }))
|
|
108
|
+
.filter(({ score }) => terms.length === 0 || score > 0)
|
|
109
|
+
.map(({ doc, score }) => ({
|
|
128
110
|
entityId: doc.entityId,
|
|
129
111
|
type: doc.type,
|
|
130
112
|
layer: doc.layer,
|
|
131
|
-
score: resultScore(
|
|
132
|
-
matches: opts.snippets === false ? [] :
|
|
113
|
+
score: resultScore(score),
|
|
114
|
+
matches: opts.snippets === false ? [] : searchMatchesFor(doc.entity, query),
|
|
133
115
|
entity: doc.entity,
|
|
134
116
|
}))
|
|
135
117
|
.sort((a, b) => b.score - a.score)
|
|
136
118
|
.slice(0, opts.limit ?? DEFAULT_LIMIT);
|
|
137
119
|
}
|
|
120
|
+
async function searchWithDiskIndex(file, opts, loadOpts) {
|
|
121
|
+
const { searchWithSqlite } = await import("./sqlite-search.js");
|
|
122
|
+
return searchWithSqlite(file, opts, loadOpts);
|
|
123
|
+
}
|
|
124
|
+
export async function searchDrawing(file, opts = {}, loadOpts = {}) {
|
|
125
|
+
return ((await searchWithDiskIndex(file, opts, loadOpts)) ??
|
|
126
|
+
searchWithScan(file, opts, loadOpts));
|
|
127
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, rmSync } from "node:fs";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { basename, dirname, join } from "node:path";
|
|
4
|
+
import { getCacheDir } from "../store.js";
|
|
5
|
+
import { loadDrawing } from "./drawing.js";
|
|
6
|
+
import { entitySearchFields, searchMatchesFor, } from "./search.js";
|
|
7
|
+
import { computeDrawingFingerprint } from "./search-cache.js";
|
|
8
|
+
const require = createRequire(import.meta.url);
|
|
9
|
+
const SEARCH_SCHEMA_VERSION = 1;
|
|
10
|
+
function sqliteSearchDatabasePath(file, cacheDir) {
|
|
11
|
+
const root = cacheDir ?? getCacheDir();
|
|
12
|
+
const key = computeDrawingFingerprint(file);
|
|
13
|
+
return join(root, "search", `${basename(file)}-${key}.search.sqlite`);
|
|
14
|
+
}
|
|
15
|
+
async function openSqliteDatabase(path) {
|
|
16
|
+
try {
|
|
17
|
+
const Database = require("better-sqlite3");
|
|
18
|
+
return new Database(path);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function ensureSqliteSchema(db) {
|
|
25
|
+
db.exec(`
|
|
26
|
+
PRAGMA journal_mode = WAL;
|
|
27
|
+
CREATE TABLE IF NOT EXISTS meta (
|
|
28
|
+
key TEXT PRIMARY KEY,
|
|
29
|
+
value TEXT NOT NULL
|
|
30
|
+
);
|
|
31
|
+
CREATE TABLE IF NOT EXISTS entities (
|
|
32
|
+
id INTEGER PRIMARY KEY,
|
|
33
|
+
entity_id TEXT NOT NULL,
|
|
34
|
+
type TEXT NOT NULL,
|
|
35
|
+
layer TEXT,
|
|
36
|
+
text TEXT NOT NULL,
|
|
37
|
+
entity_json TEXT NOT NULL
|
|
38
|
+
);
|
|
39
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS entities_fts USING fts5(
|
|
40
|
+
entity_id,
|
|
41
|
+
type,
|
|
42
|
+
layer,
|
|
43
|
+
text
|
|
44
|
+
);
|
|
45
|
+
`);
|
|
46
|
+
}
|
|
47
|
+
function sqliteCacheIsValid(db, fingerprint) {
|
|
48
|
+
const row = db
|
|
49
|
+
.prepare("SELECT value FROM meta WHERE key = ?")
|
|
50
|
+
.get("fingerprint");
|
|
51
|
+
const version = db
|
|
52
|
+
.prepare("SELECT value FROM meta WHERE key = ?")
|
|
53
|
+
.get("schema_version");
|
|
54
|
+
return (row?.value === fingerprint &&
|
|
55
|
+
version?.value === String(SEARCH_SCHEMA_VERSION));
|
|
56
|
+
}
|
|
57
|
+
function resetSqliteDatabase(path) {
|
|
58
|
+
for (const suffix of ["", "-wal", "-shm"]) {
|
|
59
|
+
const target = `${path}${suffix}`;
|
|
60
|
+
if (existsSync(target))
|
|
61
|
+
rmSync(target, { force: true });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function indexedEntities(doc) {
|
|
65
|
+
return doc.entities.map((entity, id) => ({
|
|
66
|
+
id,
|
|
67
|
+
entityId: entity.id,
|
|
68
|
+
type: entity.type,
|
|
69
|
+
layer: entity.layer,
|
|
70
|
+
text: entitySearchFields(entity).join(" "),
|
|
71
|
+
entity,
|
|
72
|
+
}));
|
|
73
|
+
}
|
|
74
|
+
function writeSqliteSearchDatabase(db, fingerprint, doc) {
|
|
75
|
+
ensureSqliteSchema(db);
|
|
76
|
+
const insertMeta = db.prepare("INSERT INTO meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value");
|
|
77
|
+
const insertEntity = db.prepare(`
|
|
78
|
+
INSERT INTO entities (id, entity_id, type, layer, text, entity_json)
|
|
79
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
80
|
+
`);
|
|
81
|
+
const insertFts = db.prepare(`
|
|
82
|
+
INSERT INTO entities_fts (rowid, entity_id, type, layer, text)
|
|
83
|
+
VALUES (?, ?, ?, ?, ?)
|
|
84
|
+
`);
|
|
85
|
+
db.exec("BEGIN");
|
|
86
|
+
try {
|
|
87
|
+
insertMeta.run("schema_version", String(SEARCH_SCHEMA_VERSION));
|
|
88
|
+
insertMeta.run("fingerprint", fingerprint);
|
|
89
|
+
for (const entity of indexedEntities(doc)) {
|
|
90
|
+
insertEntity.run(entity.id, entity.entityId, entity.type, entity.layer ?? null, entity.text, JSON.stringify(entity.entity));
|
|
91
|
+
insertFts.run(entity.id, entity.entityId, entity.type, entity.layer ?? "", entity.text);
|
|
92
|
+
}
|
|
93
|
+
db.exec("COMMIT");
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
db.exec("ROLLBACK");
|
|
97
|
+
db.close();
|
|
98
|
+
throw error;
|
|
99
|
+
}
|
|
100
|
+
return db;
|
|
101
|
+
}
|
|
102
|
+
async function openSqliteSearchDatabase(file, opts, loadOpts) {
|
|
103
|
+
const fingerprint = computeDrawingFingerprint(file);
|
|
104
|
+
const path = sqliteSearchDatabasePath(file, opts.cacheDir);
|
|
105
|
+
if (existsSync(path)) {
|
|
106
|
+
try {
|
|
107
|
+
const db = await openSqliteDatabase(path);
|
|
108
|
+
if (!db)
|
|
109
|
+
return null;
|
|
110
|
+
ensureSqliteSchema(db);
|
|
111
|
+
if (sqliteCacheIsValid(db, fingerprint))
|
|
112
|
+
return db;
|
|
113
|
+
db.close();
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
resetSqliteDatabase(path);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
resetSqliteDatabase(path);
|
|
120
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
121
|
+
const db = await openSqliteDatabase(path);
|
|
122
|
+
if (!db)
|
|
123
|
+
return null;
|
|
124
|
+
return writeSqliteSearchDatabase(db, fingerprint, await loadDrawing(file, loadOpts));
|
|
125
|
+
}
|
|
126
|
+
function matchesFilterSql(opts) {
|
|
127
|
+
const clauses = [];
|
|
128
|
+
if (opts.type)
|
|
129
|
+
clauses.push("lower(e.type) = lower(:type)");
|
|
130
|
+
if (opts.layer)
|
|
131
|
+
clauses.push("lower(e.layer) = lower(:layer)");
|
|
132
|
+
return clauses.length > 0 ? ` AND ${clauses.join(" AND ")}` : "";
|
|
133
|
+
}
|
|
134
|
+
function ftsQuery(query) {
|
|
135
|
+
return query
|
|
136
|
+
.split(/\s+/)
|
|
137
|
+
.map((term) => term.trim())
|
|
138
|
+
.filter(Boolean)
|
|
139
|
+
.map((term) => `"${term.replaceAll('"', '""')}"`)
|
|
140
|
+
.join(" OR ");
|
|
141
|
+
}
|
|
142
|
+
function parseEntity(json) {
|
|
143
|
+
try {
|
|
144
|
+
const value = JSON.parse(json);
|
|
145
|
+
if (!value ||
|
|
146
|
+
typeof value.id !== "string" ||
|
|
147
|
+
typeof value.type !== "string") {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
return value;
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
function resultScore(score) {
|
|
157
|
+
return Math.round((Number(score) || 1) * 10) / 10;
|
|
158
|
+
}
|
|
159
|
+
function rowToResult(row, query) {
|
|
160
|
+
const entity = parseEntity(row.entity_json);
|
|
161
|
+
if (!entity)
|
|
162
|
+
return null;
|
|
163
|
+
return {
|
|
164
|
+
entityId: row.entity_id,
|
|
165
|
+
type: row.type,
|
|
166
|
+
layer: row.layer ?? undefined,
|
|
167
|
+
score: resultScore(row.score),
|
|
168
|
+
matches: searchMatchesFor(entity, query),
|
|
169
|
+
entity,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
export async function searchWithSqlite(file, opts, loadOpts) {
|
|
173
|
+
const db = await openSqliteSearchDatabase(file, opts, loadOpts);
|
|
174
|
+
if (!db)
|
|
175
|
+
return null;
|
|
176
|
+
const query = opts.query?.trim();
|
|
177
|
+
const limit = opts.limit ?? 30;
|
|
178
|
+
const filter = matchesFilterSql(opts);
|
|
179
|
+
const params = {
|
|
180
|
+
query: query ? ftsQuery(query) : "",
|
|
181
|
+
limit,
|
|
182
|
+
};
|
|
183
|
+
if (opts.type)
|
|
184
|
+
params.type = opts.type;
|
|
185
|
+
if (opts.layer)
|
|
186
|
+
params.layer = opts.layer;
|
|
187
|
+
const rows = query
|
|
188
|
+
? db
|
|
189
|
+
.prepare(`
|
|
190
|
+
SELECT e.id, e.entity_id, e.type, e.layer, e.text, e.entity_json,
|
|
191
|
+
-bm25(entities_fts, 3.0, 2.0, 2.0, 1.0) AS score
|
|
192
|
+
FROM entities_fts
|
|
193
|
+
JOIN entities e ON e.id = entities_fts.rowid
|
|
194
|
+
WHERE entities_fts MATCH :query${filter}
|
|
195
|
+
ORDER BY bm25(entities_fts, 3.0, 2.0, 2.0, 1.0)
|
|
196
|
+
LIMIT :limit
|
|
197
|
+
`)
|
|
198
|
+
.all(params)
|
|
199
|
+
: db
|
|
200
|
+
.prepare(`
|
|
201
|
+
SELECT e.id, e.entity_id, e.type, e.layer, e.text, e.entity_json,
|
|
202
|
+
1 AS score
|
|
203
|
+
FROM entities e
|
|
204
|
+
WHERE 1 = 1${filter}
|
|
205
|
+
ORDER BY e.id
|
|
206
|
+
LIMIT :limit
|
|
207
|
+
`)
|
|
208
|
+
.all(params);
|
|
209
|
+
db.close();
|
|
210
|
+
return rows
|
|
211
|
+
.map((row) => {
|
|
212
|
+
const result = rowToResult(row, query);
|
|
213
|
+
return result && opts.snippets === false
|
|
214
|
+
? { ...result, matches: [] }
|
|
215
|
+
: result;
|
|
216
|
+
})
|
|
217
|
+
.filter((result) => Boolean(result));
|
|
218
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@miclivs/cadcli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Agent-friendly CAD inspection, search, viewing, and editing for DWG/DXF files.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -17,11 +17,13 @@
|
|
|
17
17
|
"files": [
|
|
18
18
|
"dist",
|
|
19
19
|
"patches",
|
|
20
|
+
"scripts",
|
|
20
21
|
"skills"
|
|
21
22
|
],
|
|
22
23
|
"scripts": {
|
|
23
24
|
"build": "tsc",
|
|
24
25
|
"prepublishOnly": "npm run build",
|
|
26
|
+
"postinstall": "node scripts/apply-acad-ts-patch.mjs",
|
|
25
27
|
"ci": "bun test --coverage && bun run build && bunx biome check src/",
|
|
26
28
|
"dev": "bun run src/main.ts",
|
|
27
29
|
"build:bun": "bun build --compile src/main.ts --outfile cadcli",
|
|
@@ -67,9 +69,11 @@
|
|
|
67
69
|
"@node-projects/acad-ts": "2.3.0",
|
|
68
70
|
"chalk": "^5.6.2",
|
|
69
71
|
"commander": "^14.0.3",
|
|
70
|
-
"minisearch": "^7.2.0",
|
|
71
72
|
"nanoid": "^5.1.6"
|
|
72
73
|
},
|
|
74
|
+
"optionalDependencies": {
|
|
75
|
+
"better-sqlite3": "^12.10.0"
|
|
76
|
+
},
|
|
73
77
|
"patchedDependencies": {
|
|
74
78
|
"@node-projects/acad-ts@2.3.0": "patches/@node-projects%2Facad-ts@2.3.0.patch"
|
|
75
79
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
const root = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
8
|
+
const targets = [
|
|
9
|
+
join(root, "node_modules"),
|
|
10
|
+
dirname(dirname(root)),
|
|
11
|
+
].map((nodeModulesRoot) =>
|
|
12
|
+
join(
|
|
13
|
+
nodeModulesRoot,
|
|
14
|
+
"@node-projects",
|
|
15
|
+
"acad-ts",
|
|
16
|
+
"dist",
|
|
17
|
+
"IO",
|
|
18
|
+
"DWG",
|
|
19
|
+
"DwgStreamReaders",
|
|
20
|
+
"DwgObjectReader.js",
|
|
21
|
+
),
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const copied = "new Uint8Array(this._memoryStream)";
|
|
25
|
+
const reused = "this._memoryStream";
|
|
26
|
+
|
|
27
|
+
for (const target of targets) {
|
|
28
|
+
let source;
|
|
29
|
+
try {
|
|
30
|
+
source = readFileSync(target, "utf8");
|
|
31
|
+
} catch {
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const matches = source.split(copied).length - 1;
|
|
36
|
+
if (matches === 0) process.exit(0);
|
|
37
|
+
|
|
38
|
+
writeFileSync(target, source.replaceAll(copied, reused));
|
|
39
|
+
console.log(`cadcli: patched acad-ts DwgObjectReader (${matches} buffer copies removed)`);
|
|
40
|
+
process.exit(0);
|
|
41
|
+
}
|