@dennisrongo/dsh-todo 0.2.0 → 0.3.0
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 +248 -17
- package/lib/bin.js +504 -0
- package/lib/cli.js +513 -0
- package/lib/client.js +275 -45
- package/lib/index.js +197 -81
- package/lib/typert.host.js +7 -4
- package/package.json +12 -2
package/lib/index.js
CHANGED
|
@@ -48,22 +48,181 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
|
|
|
48
48
|
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
|
|
49
49
|
|
|
50
50
|
// src/index.ts
|
|
51
|
-
import {
|
|
52
|
-
import {
|
|
53
|
-
import { isAbsolute, join, resolve } from "node:path";
|
|
51
|
+
import { readFileSync, renameSync, existsSync } from "node:fs";
|
|
52
|
+
import { isAbsolute, join as join2, resolve as resolve2 } from "node:path";
|
|
54
53
|
import { Service } from "@deepseek-ai/cordis";
|
|
55
54
|
import { Remote, TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
|
|
56
55
|
import { z } from "zod";
|
|
57
56
|
|
|
57
|
+
// src/db.ts
|
|
58
|
+
import { mkdirSync } from "node:fs";
|
|
59
|
+
import { DatabaseSync } from "node:sqlite";
|
|
60
|
+
import { join, resolve } from "node:path";
|
|
61
|
+
|
|
58
62
|
// src/types.ts
|
|
63
|
+
var STATUSES = ["backlog", "todo", "in-progress", "blocked", "done"];
|
|
64
|
+
var DEFAULT_STATUS = "todo";
|
|
65
|
+
var PRIORITIES = ["p0", "p1", "p2", "p3"];
|
|
66
|
+
var DEFAULT_PRIORITY = "p2";
|
|
67
|
+
function toStatus(value) {
|
|
68
|
+
return typeof value === "string" && STATUSES.includes(value) ? value : DEFAULT_STATUS;
|
|
69
|
+
}
|
|
70
|
+
__name(toStatus, "toStatus");
|
|
71
|
+
function toPriority(value) {
|
|
72
|
+
return typeof value === "string" && PRIORITIES.includes(value) ? value : DEFAULT_PRIORITY;
|
|
73
|
+
}
|
|
74
|
+
__name(toPriority, "toPriority");
|
|
75
|
+
function normalizeLabel(raw) {
|
|
76
|
+
if (typeof raw !== "string") return void 0;
|
|
77
|
+
const text = raw.replace(/\s+/g, " ").trim().slice(0, MAX_LABEL);
|
|
78
|
+
return text.length > 0 ? text : void 0;
|
|
79
|
+
}
|
|
80
|
+
__name(normalizeLabel, "normalizeLabel");
|
|
81
|
+
var DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
82
|
+
function normalizeDueDate(raw) {
|
|
83
|
+
if (typeof raw !== "string") return void 0;
|
|
84
|
+
const text = raw.trim();
|
|
85
|
+
if (!DATE_RE.test(text)) return void 0;
|
|
86
|
+
const parsed = /* @__PURE__ */ new Date(`${text}T00:00:00Z`);
|
|
87
|
+
if (Number.isNaN(parsed.getTime())) return void 0;
|
|
88
|
+
return parsed.toISOString().slice(0, 10) === text ? text : void 0;
|
|
89
|
+
}
|
|
90
|
+
__name(normalizeDueDate, "normalizeDueDate");
|
|
59
91
|
var MAX_TEXT = 500;
|
|
92
|
+
var MAX_DESC = 5e3;
|
|
93
|
+
var MAX_LABEL = 60;
|
|
60
94
|
var MAX_ITEMS = 1e3;
|
|
61
95
|
|
|
96
|
+
// src/db.ts
|
|
97
|
+
var DOT_DSH = ".dsh";
|
|
98
|
+
var DB_FILE = "todo.db";
|
|
99
|
+
var BUSY_TIMEOUT_MS = 5e3;
|
|
100
|
+
function openDb(dir) {
|
|
101
|
+
const resolved = resolve(dir);
|
|
102
|
+
mkdirSync(join(resolved, DOT_DSH), { recursive: true });
|
|
103
|
+
const db = new DatabaseSync(join(resolved, DOT_DSH, DB_FILE));
|
|
104
|
+
db.exec(`PRAGMA busy_timeout = ${BUSY_TIMEOUT_MS}`);
|
|
105
|
+
db.exec(`
|
|
106
|
+
CREATE TABLE IF NOT EXISTS todo (
|
|
107
|
+
id TEXT PRIMARY KEY,
|
|
108
|
+
text TEXT NOT NULL,
|
|
109
|
+
done INTEGER NOT NULL DEFAULT 0,
|
|
110
|
+
created_at INTEGER NOT NULL,
|
|
111
|
+
completed_at INTEGER,
|
|
112
|
+
archived_at INTEGER,
|
|
113
|
+
position INTEGER NOT NULL
|
|
114
|
+
);
|
|
115
|
+
CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT NOT NULL);
|
|
116
|
+
`);
|
|
117
|
+
migrateSchema(db);
|
|
118
|
+
return db;
|
|
119
|
+
}
|
|
120
|
+
__name(openDb, "openDb");
|
|
121
|
+
function migrateSchema(db) {
|
|
122
|
+
const columns = new Set(
|
|
123
|
+
db.prepare("PRAGMA table_info(todo)").all().map((c) => String(c.name))
|
|
124
|
+
);
|
|
125
|
+
const add = /* @__PURE__ */ __name((name, ddl) => {
|
|
126
|
+
if (columns.has(name)) return false;
|
|
127
|
+
db.exec(`ALTER TABLE todo ADD COLUMN ${ddl}`);
|
|
128
|
+
columns.add(name);
|
|
129
|
+
return true;
|
|
130
|
+
}, "add");
|
|
131
|
+
const addedTitle = add("title", "title TEXT");
|
|
132
|
+
const addedStatus = add("status", "status TEXT");
|
|
133
|
+
add("description", "description TEXT");
|
|
134
|
+
add("priority", "priority TEXT");
|
|
135
|
+
add("release", "release TEXT");
|
|
136
|
+
add("sprint", "sprint TEXT");
|
|
137
|
+
add("due_date", "due_date TEXT");
|
|
138
|
+
if (addedTitle && columns.has("text")) {
|
|
139
|
+
db.exec("UPDATE todo SET title = text WHERE title IS NULL");
|
|
140
|
+
}
|
|
141
|
+
if (addedStatus && columns.has("done")) {
|
|
142
|
+
db.exec("UPDATE todo SET status = CASE WHEN done = 1 THEN 'done' ELSE 'todo' END WHERE status IS NULL");
|
|
143
|
+
}
|
|
144
|
+
db.exec("UPDATE todo SET status = 'todo' WHERE status IS NULL OR status = ''");
|
|
145
|
+
db.exec("UPDATE todo SET priority = 'p2' WHERE priority IS NULL OR priority = ''");
|
|
146
|
+
db.exec("UPDATE todo SET title = '' WHERE title IS NULL");
|
|
147
|
+
}
|
|
148
|
+
__name(migrateSchema, "migrateSchema");
|
|
149
|
+
function readList(db) {
|
|
150
|
+
const revision = Number(db.prepare("SELECT value FROM meta WHERE key = 'revision'").get()?.value ?? 0);
|
|
151
|
+
const updatedAt = Number(db.prepare("SELECT value FROM meta WHERE key = 'updatedAt'").get()?.value ?? 0);
|
|
152
|
+
const rows = db.prepare(
|
|
153
|
+
`SELECT id, title, description, status, priority, release, sprint, due_date,
|
|
154
|
+
created_at, completed_at, archived_at
|
|
155
|
+
FROM todo ORDER BY position ASC`
|
|
156
|
+
).all();
|
|
157
|
+
const text = /* @__PURE__ */ __name((v) => v === null || v === void 0 ? void 0 : String(v), "text");
|
|
158
|
+
const items = [];
|
|
159
|
+
for (const row of rows) {
|
|
160
|
+
items.push({
|
|
161
|
+
id: String(row.id),
|
|
162
|
+
title: String(row.title ?? ""),
|
|
163
|
+
status: toStatus(row.status),
|
|
164
|
+
priority: toPriority(row.priority),
|
|
165
|
+
...text(row.description) !== void 0 ? { description: text(row.description) } : {},
|
|
166
|
+
...normalizeLabel(row.release) !== void 0 ? { release: normalizeLabel(row.release) } : {},
|
|
167
|
+
...normalizeLabel(row.sprint) !== void 0 ? { sprint: normalizeLabel(row.sprint) } : {},
|
|
168
|
+
...normalizeDueDate(row.due_date) !== void 0 ? { dueDate: normalizeDueDate(row.due_date) } : {},
|
|
169
|
+
createdAt: Number(row.created_at),
|
|
170
|
+
...row.completed_at !== null && row.completed_at !== void 0 ? { completedAt: Number(row.completed_at) } : {},
|
|
171
|
+
...row.archived_at !== null && row.archived_at !== void 0 ? { archivedAt: Number(row.archived_at) } : {}
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
return { items, revision, updatedAt };
|
|
175
|
+
}
|
|
176
|
+
__name(readList, "readList");
|
|
177
|
+
function writeList(db, items, revision, updatedAt = Date.now()) {
|
|
178
|
+
db.exec("BEGIN IMMEDIATE");
|
|
179
|
+
try {
|
|
180
|
+
db.prepare("DELETE FROM todo").run();
|
|
181
|
+
const insert = db.prepare(
|
|
182
|
+
`INSERT INTO todo (id, title, description, status, priority, release, sprint, due_date,
|
|
183
|
+
text, done, created_at, completed_at, archived_at, position)
|
|
184
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
185
|
+
);
|
|
186
|
+
items.forEach((item, index) => {
|
|
187
|
+
insert.run(
|
|
188
|
+
item.id,
|
|
189
|
+
item.title,
|
|
190
|
+
item.description ?? null,
|
|
191
|
+
item.status,
|
|
192
|
+
item.priority,
|
|
193
|
+
item.release ?? null,
|
|
194
|
+
item.sprint ?? null,
|
|
195
|
+
item.dueDate ?? null,
|
|
196
|
+
item.title,
|
|
197
|
+
item.status === "done" ? 1 : 0,
|
|
198
|
+
item.createdAt,
|
|
199
|
+
item.completedAt ?? null,
|
|
200
|
+
item.archivedAt ?? null,
|
|
201
|
+
index
|
|
202
|
+
);
|
|
203
|
+
});
|
|
204
|
+
db.prepare(`INSERT INTO meta (key, value) VALUES ('revision', ?)
|
|
205
|
+
ON CONFLICT(key) DO UPDATE SET value = excluded.value`).run(String(revision));
|
|
206
|
+
db.prepare(`INSERT INTO meta (key, value) VALUES ('updatedAt', ?)
|
|
207
|
+
ON CONFLICT(key) DO UPDATE SET value = excluded.value`).run(String(updatedAt));
|
|
208
|
+
db.exec("COMMIT");
|
|
209
|
+
} catch (err) {
|
|
210
|
+
db.exec("ROLLBACK");
|
|
211
|
+
throw err;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
__name(writeList, "writeList");
|
|
215
|
+
|
|
62
216
|
// src/index.ts
|
|
63
217
|
var todoItemSchema = z.object({
|
|
64
218
|
id: z.string().min(1),
|
|
65
|
-
|
|
66
|
-
|
|
219
|
+
title: z.string().max(MAX_TEXT),
|
|
220
|
+
description: z.string().max(MAX_DESC).optional(),
|
|
221
|
+
status: z.enum(["backlog", "todo", "in-progress", "blocked", "done"]),
|
|
222
|
+
priority: z.enum(["p0", "p1", "p2", "p3"]),
|
|
223
|
+
release: z.string().max(MAX_LABEL).optional(),
|
|
224
|
+
sprint: z.string().max(MAX_LABEL).optional(),
|
|
225
|
+
dueDate: z.string().optional(),
|
|
67
226
|
createdAt: z.number(),
|
|
68
227
|
completedAt: z.number().optional(),
|
|
69
228
|
archivedAt: z.number().optional()
|
|
@@ -77,8 +236,6 @@ var todoDomainSpec = {
|
|
|
77
236
|
name: "dsh_todo",
|
|
78
237
|
version: 2
|
|
79
238
|
};
|
|
80
|
-
var DOT_DSH = ".dsh";
|
|
81
|
-
var DB_FILE = "todo.db";
|
|
82
239
|
var _replace_dec, _list_dec, _a, _init, _b;
|
|
83
240
|
var _TodoService = class _TodoService extends (_b = TypertRemoteService) {
|
|
84
241
|
/**
|
|
@@ -110,8 +267,8 @@ var _TodoService = class _TodoService extends (_b = TypertRemoteService) {
|
|
|
110
267
|
this.ctx.effect(() => () => this.close(), "dsh-todo: close workspace databases");
|
|
111
268
|
const home = process.env.DSH_HOME;
|
|
112
269
|
if (!home) return;
|
|
113
|
-
const legacyPath =
|
|
114
|
-
const registryPath =
|
|
270
|
+
const legacyPath = join2(home, "storages", "dsh_todo.json");
|
|
271
|
+
const registryPath = join2(home, "storages", "workspace.json");
|
|
115
272
|
try {
|
|
116
273
|
if (!existsSync(legacyPath)) return;
|
|
117
274
|
const legacy = JSON.parse(readFileSync(legacyPath, "utf8"));
|
|
@@ -160,7 +317,7 @@ var _TodoService = class _TodoService extends (_b = TypertRemoteService) {
|
|
|
160
317
|
const registry = this.ctx.workspaceRegistry;
|
|
161
318
|
const workspace = registry.list().find((w) => String(w.id) === workspaceId);
|
|
162
319
|
if (workspace === void 0) throw new Error(`dsh-todo: unknown workspace ${workspaceId}`);
|
|
163
|
-
const dir =
|
|
320
|
+
const dir = resolve2(workspace.path);
|
|
164
321
|
this.dirs.set(workspaceId, dir);
|
|
165
322
|
return dir;
|
|
166
323
|
}
|
|
@@ -171,23 +328,10 @@ var _TodoService = class _TodoService extends (_b = TypertRemoteService) {
|
|
|
171
328
|
* cloned since.
|
|
172
329
|
*/
|
|
173
330
|
openDb(dir) {
|
|
174
|
-
const resolved =
|
|
331
|
+
const resolved = resolve2(dir);
|
|
175
332
|
const cached = this.dbs.get(resolved);
|
|
176
333
|
if (cached) return cached;
|
|
177
|
-
|
|
178
|
-
const db = new DatabaseSync(join(resolved, DOT_DSH, DB_FILE));
|
|
179
|
-
db.exec(`
|
|
180
|
-
CREATE TABLE IF NOT EXISTS todo (
|
|
181
|
-
id TEXT PRIMARY KEY,
|
|
182
|
-
text TEXT NOT NULL,
|
|
183
|
-
done INTEGER NOT NULL DEFAULT 0,
|
|
184
|
-
created_at INTEGER NOT NULL,
|
|
185
|
-
completed_at INTEGER,
|
|
186
|
-
archived_at INTEGER,
|
|
187
|
-
position INTEGER NOT NULL
|
|
188
|
-
);
|
|
189
|
-
CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT NOT NULL);
|
|
190
|
-
`);
|
|
334
|
+
const db = openDb(resolved);
|
|
191
335
|
this.dbs.set(resolved, db);
|
|
192
336
|
return db;
|
|
193
337
|
}
|
|
@@ -249,61 +393,22 @@ var _TodoService = class _TodoService extends (_b = TypertRemoteService) {
|
|
|
249
393
|
}
|
|
250
394
|
this.dbs.clear();
|
|
251
395
|
}
|
|
252
|
-
/**
|
|
396
|
+
/**
|
|
397
|
+
* Read the whole list, then re-validate it at this service's own boundary.
|
|
398
|
+
*
|
|
399
|
+
* `db.readList` already coerces rows, but the host keeps the zod check: it is
|
|
400
|
+
* the durable read boundary for the WIRE, and a shape the schema rejects must
|
|
401
|
+
* not reach the browser even if the storage layer was willing to build it.
|
|
402
|
+
*/
|
|
253
403
|
readList(db) {
|
|
254
|
-
const
|
|
255
|
-
const
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
FROM todo ORDER BY position ASC`
|
|
259
|
-
).all();
|
|
260
|
-
const items = [];
|
|
261
|
-
for (const row of rows) {
|
|
262
|
-
const candidate = {
|
|
263
|
-
id: String(row.id),
|
|
264
|
-
text: String(row.text),
|
|
265
|
-
done: Number(row.done) === 1,
|
|
266
|
-
createdAt: Number(row.created_at),
|
|
267
|
-
...row.completed_at !== null && row.completed_at !== void 0 ? { completedAt: Number(row.completed_at) } : {},
|
|
268
|
-
...row.archived_at !== null && row.archived_at !== void 0 ? { archivedAt: Number(row.archived_at) } : {}
|
|
269
|
-
};
|
|
270
|
-
const parsed = todoItemSchema.safeParse(candidate);
|
|
271
|
-
if (parsed.success) items.push(parsed.data);
|
|
272
|
-
}
|
|
273
|
-
const list = { items, revision, updatedAt };
|
|
274
|
-
const check = todoListSchema.safeParse(list);
|
|
275
|
-
return check.success ? list : { items: [], revision, updatedAt };
|
|
404
|
+
const list = readList(db);
|
|
405
|
+
const items = list.items.filter((item) => todoItemSchema.safeParse(item).success);
|
|
406
|
+
const checked = { items, revision: list.revision, updatedAt: list.updatedAt };
|
|
407
|
+
return todoListSchema.safeParse(checked).success ? checked : { items: [], revision: list.revision, updatedAt: list.updatedAt };
|
|
276
408
|
}
|
|
277
409
|
/** Replace every row inside one transaction and stamp the meta tokens. */
|
|
278
410
|
writeList(db, items, revision, updatedAt = Date.now()) {
|
|
279
|
-
|
|
280
|
-
db.exec("BEGIN");
|
|
281
|
-
try {
|
|
282
|
-
db.prepare("DELETE FROM todo").run();
|
|
283
|
-
const insert = db.prepare(
|
|
284
|
-
`INSERT INTO todo (id, text, done, created_at, completed_at, archived_at, position)
|
|
285
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)`
|
|
286
|
-
);
|
|
287
|
-
items.forEach((item, index) => {
|
|
288
|
-
insert.run(
|
|
289
|
-
item.id,
|
|
290
|
-
item.text,
|
|
291
|
-
item.done ? 1 : 0,
|
|
292
|
-
item.createdAt,
|
|
293
|
-
item.completedAt ?? null,
|
|
294
|
-
item.archivedAt ?? null,
|
|
295
|
-
index
|
|
296
|
-
);
|
|
297
|
-
});
|
|
298
|
-
db.prepare(`INSERT INTO meta (key, value) VALUES ('revision', ?)
|
|
299
|
-
ON CONFLICT(key) DO UPDATE SET value = excluded.value`).run(String(revision));
|
|
300
|
-
db.prepare(`INSERT INTO meta (key, value) VALUES ('updatedAt', ?)
|
|
301
|
-
ON CONFLICT(key) DO UPDATE SET value = excluded.value`).run(String(now));
|
|
302
|
-
db.exec("COMMIT");
|
|
303
|
-
} catch (err) {
|
|
304
|
-
db.exec("ROLLBACK");
|
|
305
|
-
throw err;
|
|
306
|
-
}
|
|
411
|
+
writeList(db, items, revision, updatedAt);
|
|
307
412
|
}
|
|
308
413
|
};
|
|
309
414
|
_init = __decoratorStart(_b);
|
|
@@ -324,18 +429,29 @@ function sanitizeItems(value) {
|
|
|
324
429
|
if (!entry || typeof entry !== "object") continue;
|
|
325
430
|
const e = entry;
|
|
326
431
|
if (typeof e.id !== "string" || e.id.length === 0) continue;
|
|
327
|
-
|
|
432
|
+
const rawTitle = typeof e.title === "string" ? e.title : e.text;
|
|
433
|
+
if (typeof rawTitle !== "string") continue;
|
|
328
434
|
if (seen.has(e.id)) continue;
|
|
329
435
|
seen.add(e.id);
|
|
330
|
-
const
|
|
436
|
+
const status = e.status === void 0 && e.done === true ? "done" : toStatus(e.status);
|
|
437
|
+
const done = status === "done";
|
|
438
|
+
const description = typeof e.description === "string" && e.description.length > 0 ? e.description.slice(0, MAX_DESC) : void 0;
|
|
439
|
+
const release = normalizeLabel(e.release);
|
|
440
|
+
const sprint = normalizeLabel(e.sprint);
|
|
441
|
+
const dueDate = normalizeDueDate(e.dueDate);
|
|
331
442
|
const completedAt = typeof e.completedAt === "number" ? e.completedAt : void 0;
|
|
332
443
|
const archivedAt = typeof e.archivedAt === "number" ? e.archivedAt : void 0;
|
|
333
444
|
out.push({
|
|
334
445
|
id: e.id,
|
|
335
|
-
|
|
336
|
-
|
|
446
|
+
title: rawTitle.slice(0, MAX_TEXT),
|
|
447
|
+
status,
|
|
448
|
+
priority: toPriority(e.priority),
|
|
449
|
+
...description !== void 0 ? { description } : {},
|
|
450
|
+
...release !== void 0 ? { release } : {},
|
|
451
|
+
...sprint !== void 0 ? { sprint } : {},
|
|
452
|
+
...dueDate !== void 0 ? { dueDate } : {},
|
|
337
453
|
createdAt: typeof e.createdAt === "number" ? e.createdAt : 0,
|
|
338
|
-
// completedAt is meaningless on an
|
|
454
|
+
// completedAt is meaningless on an unfinished item; drop it rather than store a lie.
|
|
339
455
|
...done && completedAt !== void 0 ? { completedAt } : {},
|
|
340
456
|
// archivedAt is the archived flag itself, so a non-numeric value must not
|
|
341
457
|
// survive as a truthy marker.
|
package/lib/typert.host.js
CHANGED
|
@@ -5,12 +5,15 @@ var __name = (target, value) => __defProp(target, "name", { value, configurable:
|
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
var todoItemSchema = z.object({
|
|
7
7
|
id: z.string(),
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
title: z.string(),
|
|
9
|
+
description: z.string().optional(),
|
|
10
|
+
status: z.enum(["backlog", "todo", "in-progress", "blocked", "done"]),
|
|
11
|
+
priority: z.enum(["p0", "p1", "p2", "p3"]),
|
|
12
|
+
release: z.string().optional(),
|
|
13
|
+
sprint: z.string().optional(),
|
|
14
|
+
dueDate: z.string().optional(),
|
|
10
15
|
createdAt: z.number(),
|
|
11
16
|
completedAt: z.number().optional(),
|
|
12
|
-
// Must be carried explicitly: these are strict codecs, so a field the schema
|
|
13
|
-
// does not name would be stripped off the wire and archiving would not persist.
|
|
14
17
|
archivedAt: z.number().optional()
|
|
15
18
|
});
|
|
16
19
|
var todoListSchema = z.object({
|
package/package.json
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dennisrongo/dsh-todo",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Todo list for DeepSeek Harness (dsh) — a per-workspace task list persisted on disk by the host",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "Dennis Rongo",
|
|
8
8
|
"main": "lib/index.js",
|
|
9
|
+
"bin": {
|
|
10
|
+
"dsh-todo": "lib/bin.js"
|
|
11
|
+
},
|
|
9
12
|
"exports": {
|
|
10
13
|
".": {
|
|
11
14
|
"default": "./lib/index.js"
|
|
@@ -16,6 +19,9 @@
|
|
|
16
19
|
"./typert": {
|
|
17
20
|
"default": "./lib/typert.host.js"
|
|
18
21
|
},
|
|
22
|
+
"./cli": {
|
|
23
|
+
"default": "./lib/cli.js"
|
|
24
|
+
},
|
|
19
25
|
"./package.json": "./package.json"
|
|
20
26
|
},
|
|
21
27
|
"files": [
|
|
@@ -28,7 +34,10 @@
|
|
|
28
34
|
"scripts": {
|
|
29
35
|
"build": "node build/build.mjs",
|
|
30
36
|
"typecheck": "tsc --noEmit",
|
|
31
|
-
"test": "node test/smoke.mjs"
|
|
37
|
+
"test": "node test/smoke.mjs && node test/cli.test.mjs",
|
|
38
|
+
"test:icons": "node test/icon-probe.mjs",
|
|
39
|
+
"test:modal": "node test/modal-probe.mjs",
|
|
40
|
+
"test:cli": "node test/cli.test.mjs"
|
|
32
41
|
},
|
|
33
42
|
"dsh": {
|
|
34
43
|
"bundle": {
|
|
@@ -58,6 +67,7 @@
|
|
|
58
67
|
"devDependencies": {
|
|
59
68
|
"@types/node": "^22.20.1",
|
|
60
69
|
"@types/react": "^18.3.0",
|
|
70
|
+
"@types/react-dom": "^18.3.0",
|
|
61
71
|
"esbuild": "^0.24.0",
|
|
62
72
|
"react": "^18.3.0",
|
|
63
73
|
"react-dom": "^18.3.0",
|