leglas 0.4.0 → 0.4.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/README.md CHANGED
@@ -286,8 +286,11 @@ trace.
286
286
  ## The interface
287
287
 
288
288
  Directions live in a rail on the left. The stage shows the active one in
289
- a framed viewport at Full, 1440, 834, or 390 wide. Rename, reorder, hide,
290
- and tag directions from the rail; layout is saved per project and
289
+ a framed viewport at Full, 1440, 834 or 390 wide. Rename, reorder, hide
290
+ and tag directions from the rail. Open the removed list to restore a
291
+ direction, delete one permanently or clear the full list. Machine-local
292
+ directions are removed from `.leglas/previews.json`; shared project config
293
+ and preview source files stay untouched. Layout is saved per project and
291
294
  survives restarts and port changes.
292
295
 
293
296
  Flipping shows a difference over time. A split shows it at once, which is
package/dist/bin.js CHANGED
@@ -973,8 +973,17 @@ async function readLocalPreviews(cwd) {
973
973
  let raw;
974
974
  try {
975
975
  raw = await readFile3(path, "utf8");
976
- } catch {
977
- return { previews: [], errors: [] };
976
+ } catch (error) {
977
+ const code = error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : null;
978
+ if (code === "ENOENT") {
979
+ return { previews: [], errors: [] };
980
+ }
981
+ return {
982
+ previews: [],
983
+ errors: [
984
+ `${LOCAL_PREVIEWS_PATH} could not be read (${code ?? "unknown error"}). Check its permissions and file type; nothing shared is lost.`
985
+ ]
986
+ };
978
987
  }
979
988
  let parsed2;
980
989
  try {
@@ -1859,13 +1868,23 @@ async function startServer(options) {
1859
1868
  const notice = configStalenessNotice(cwd, bootConfigSnapshot, snapshotConfig(cwd));
1860
1869
  if (notice !== null)
1861
1870
  errors.push(notice);
1862
- return void readLocalPreviews(cwd).then(({ previews: local }) => {
1863
- const known = new Set(boot.map((preview) => preview.title));
1871
+ return void readLocalPreviews(cwd).then(({ previews: local, errors: localErrors }) => {
1872
+ if (localErrors.length > 0) {
1873
+ return sendJson(res, 200, {
1874
+ project,
1875
+ devServer: target,
1876
+ previews: boot,
1877
+ errors
1878
+ });
1879
+ }
1880
+ const localTitles = new Set(local.map((preview) => preview.title));
1881
+ const currentBoot = boot.filter((preview) => preview.local !== true || localTitles.has(preview.title));
1882
+ const known = new Set(currentBoot.map((preview) => preview.title));
1864
1883
  const fresh = local.filter((preview) => !known.has(preview.title) && preview.branch === void 0 && preview.file === void 0);
1865
1884
  sendJson(res, 200, {
1866
1885
  project,
1867
1886
  devServer: target,
1868
- previews: [...boot, ...fresh],
1887
+ previews: [...currentBoot, ...fresh],
1869
1888
  errors
1870
1889
  });
1871
1890
  }).catch(() => sendJson(res, 200, {
@@ -1875,6 +1894,47 @@ async function startServer(options) {
1875
1894
  errors
1876
1895
  }));
1877
1896
  }
1897
+ if (path === `${LEGLAS_PREFIX}/api/previews/delete` && req.method === "POST") {
1898
+ let body = "";
1899
+ req.on("data", (chunk) => body += chunk);
1900
+ return void req.on("end", async () => {
1901
+ let parsed2;
1902
+ try {
1903
+ parsed2 = JSON.parse(body || "{}");
1904
+ } catch {
1905
+ return sendJson(res, 400, { ok: false, error: "Body must be JSON." });
1906
+ }
1907
+ const titles = parsed2.titles;
1908
+ if (!Array.isArray(titles) || titles.length === 0 || titles.some((title) => typeof title !== "string" || title.trim() === "")) {
1909
+ return sendJson(res, 400, {
1910
+ ok: false,
1911
+ error: "Body needs a non-empty array of direction titles."
1912
+ });
1913
+ }
1914
+ const unique = [...new Set(titles)];
1915
+ try {
1916
+ const local = await readLocalPreviews(cwd);
1917
+ if (local.errors.length > 0) {
1918
+ return sendJson(res, 409, { ok: false, error: local.errors.join(" ") });
1919
+ }
1920
+ const localTitles = new Set(local.previews.map((preview) => preview.title));
1921
+ const unknown = unique.filter((title) => !localTitles.has(title));
1922
+ if (unknown.length > 0) {
1923
+ return sendJson(res, 400, {
1924
+ ok: false,
1925
+ error: "Only machine-local directions can be deleted from the registry."
1926
+ });
1927
+ }
1928
+ const deleted = await dropLocalPreviews(cwd, unique);
1929
+ return sendJson(res, 200, { ok: true, deleted });
1930
+ } catch {
1931
+ return sendJson(res, 500, {
1932
+ ok: false,
1933
+ error: "The directions could not be deleted from Leglas."
1934
+ });
1935
+ }
1936
+ });
1937
+ }
1878
1938
  if (path === `${LEGLAS_PREFIX}/api/request` && req.method === "POST") {
1879
1939
  let body = "";
1880
1940
  req.on("data", (chunk) => body += chunk);
@@ -1885,8 +1945,12 @@ async function startServer(options) {
1885
1945
  } catch {
1886
1946
  return sendJson(res, 400, { ok: false, error: "Body must be JSON." });
1887
1947
  }
1888
- const local = await readLocalPreviews(cwd).then((read) => read.previews, () => []);
1889
- const preview = [...config?.previews ?? [], ...local].find((entry) => entry.title === parsed2.title);
1948
+ const localRead = await readLocalPreviews(cwd).catch(() => null);
1949
+ const local = localRead?.errors.length === 0 ? localRead.previews : [];
1950
+ const localTitles = new Set(local.map((entry) => entry.title));
1951
+ const bootConfig = config?.previews ?? [];
1952
+ const boot = localRead === null || localRead.errors.length > 0 ? bootConfig : bootConfig.filter((entry) => entry.local !== true || localTitles.has(entry.title));
1953
+ const preview = [...boot, ...local].find((entry) => entry.title === parsed2.title);
1890
1954
  if (!preview || !parsed2.intent?.trim()) {
1891
1955
  return sendJson(res, 400, { ok: false, error: "Unknown preview, or empty request." });
1892
1956
  }
package/dist/index.js CHANGED
@@ -1333,8 +1333,17 @@ async function readLocalPreviews(cwd) {
1333
1333
  let raw;
1334
1334
  try {
1335
1335
  raw = await readFile3(path, "utf8");
1336
- } catch {
1337
- return { previews: [], errors: [] };
1336
+ } catch (error) {
1337
+ const code = error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : null;
1338
+ if (code === "ENOENT") {
1339
+ return { previews: [], errors: [] };
1340
+ }
1341
+ return {
1342
+ previews: [],
1343
+ errors: [
1344
+ `${LOCAL_PREVIEWS_PATH} could not be read (${code ?? "unknown error"}). Check its permissions and file type; nothing shared is lost.`
1345
+ ]
1346
+ };
1338
1347
  }
1339
1348
  let parsed;
1340
1349
  try {
@@ -2219,13 +2228,23 @@ async function startServer(options) {
2219
2228
  const notice = configStalenessNotice(cwd, bootConfigSnapshot, snapshotConfig(cwd));
2220
2229
  if (notice !== null)
2221
2230
  errors.push(notice);
2222
- return void readLocalPreviews(cwd).then(({ previews: local }) => {
2223
- const known = new Set(boot.map((preview) => preview.title));
2231
+ return void readLocalPreviews(cwd).then(({ previews: local, errors: localErrors }) => {
2232
+ if (localErrors.length > 0) {
2233
+ return sendJson(res, 200, {
2234
+ project,
2235
+ devServer: target,
2236
+ previews: boot,
2237
+ errors
2238
+ });
2239
+ }
2240
+ const localTitles = new Set(local.map((preview) => preview.title));
2241
+ const currentBoot = boot.filter((preview) => preview.local !== true || localTitles.has(preview.title));
2242
+ const known = new Set(currentBoot.map((preview) => preview.title));
2224
2243
  const fresh = local.filter((preview) => !known.has(preview.title) && preview.branch === void 0 && preview.file === void 0);
2225
2244
  sendJson(res, 200, {
2226
2245
  project,
2227
2246
  devServer: target,
2228
- previews: [...boot, ...fresh],
2247
+ previews: [...currentBoot, ...fresh],
2229
2248
  errors
2230
2249
  });
2231
2250
  }).catch(() => sendJson(res, 200, {
@@ -2235,6 +2254,47 @@ async function startServer(options) {
2235
2254
  errors
2236
2255
  }));
2237
2256
  }
2257
+ if (path === `${LEGLAS_PREFIX}/api/previews/delete` && req.method === "POST") {
2258
+ let body = "";
2259
+ req.on("data", (chunk) => body += chunk);
2260
+ return void req.on("end", async () => {
2261
+ let parsed;
2262
+ try {
2263
+ parsed = JSON.parse(body || "{}");
2264
+ } catch {
2265
+ return sendJson(res, 400, { ok: false, error: "Body must be JSON." });
2266
+ }
2267
+ const titles = parsed.titles;
2268
+ if (!Array.isArray(titles) || titles.length === 0 || titles.some((title) => typeof title !== "string" || title.trim() === "")) {
2269
+ return sendJson(res, 400, {
2270
+ ok: false,
2271
+ error: "Body needs a non-empty array of direction titles."
2272
+ });
2273
+ }
2274
+ const unique = [...new Set(titles)];
2275
+ try {
2276
+ const local = await readLocalPreviews(cwd);
2277
+ if (local.errors.length > 0) {
2278
+ return sendJson(res, 409, { ok: false, error: local.errors.join(" ") });
2279
+ }
2280
+ const localTitles = new Set(local.previews.map((preview) => preview.title));
2281
+ const unknown = unique.filter((title) => !localTitles.has(title));
2282
+ if (unknown.length > 0) {
2283
+ return sendJson(res, 400, {
2284
+ ok: false,
2285
+ error: "Only machine-local directions can be deleted from the registry."
2286
+ });
2287
+ }
2288
+ const deleted = await dropLocalPreviews(cwd, unique);
2289
+ return sendJson(res, 200, { ok: true, deleted });
2290
+ } catch {
2291
+ return sendJson(res, 500, {
2292
+ ok: false,
2293
+ error: "The directions could not be deleted from Leglas."
2294
+ });
2295
+ }
2296
+ });
2297
+ }
2238
2298
  if (path === `${LEGLAS_PREFIX}/api/request` && req.method === "POST") {
2239
2299
  let body = "";
2240
2300
  req.on("data", (chunk) => body += chunk);
@@ -2245,8 +2305,12 @@ async function startServer(options) {
2245
2305
  } catch {
2246
2306
  return sendJson(res, 400, { ok: false, error: "Body must be JSON." });
2247
2307
  }
2248
- const local = await readLocalPreviews(cwd).then((read) => read.previews, () => []);
2249
- const preview = [...config?.previews ?? [], ...local].find((entry) => entry.title === parsed.title);
2308
+ const localRead = await readLocalPreviews(cwd).catch(() => null);
2309
+ const local = localRead?.errors.length === 0 ? localRead.previews : [];
2310
+ const localTitles = new Set(local.map((entry) => entry.title));
2311
+ const bootConfig = config?.previews ?? [];
2312
+ const boot = localRead === null || localRead.errors.length > 0 ? bootConfig : bootConfig.filter((entry) => entry.local !== true || localTitles.has(entry.title));
2313
+ const preview = [...boot, ...local].find((entry) => entry.title === parsed.title);
2250
2314
  if (!preview || !parsed.intent?.trim()) {
2251
2315
  return sendJson(res, 400, { ok: false, error: "Unknown preview, or empty request." });
2252
2316
  }