@silicajs/core 0.5.1 → 0.6.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/index.js CHANGED
@@ -10,6 +10,7 @@ import {
10
10
  asFullSlug,
11
11
  asRelativeURL,
12
12
  asSimpleSlug,
13
+ createAssetResolutionIndex,
13
14
  createWikiLinkResolutionIndex,
14
15
  generateDescriptionFromContent,
15
16
  getDescription,
@@ -18,21 +19,25 @@ import {
18
19
  hasNumericPrefixInPath,
19
20
  hrefToSlug,
20
21
  joinSegments,
22
+ normalizeAssetReference,
21
23
  normalizePath,
22
24
  normalizeSlug,
23
25
  numericPrefixSortKey,
24
26
  pathToRoot,
25
27
  renderMarkdown,
26
28
  renderMarkdownHtml,
29
+ resolveAssetPath,
27
30
  resolveRelative,
31
+ resolveRelativeAsset,
28
32
  resolveWikiLink,
29
33
  simplifySlug,
30
34
  slugToHref,
35
+ slugifyAssetPath,
31
36
  slugifyFilePath,
32
37
  slugifySegment,
33
38
  stripNumericPrefix,
34
39
  tagToHref
35
- } from "./chunk-2TXWBKM4.js";
40
+ } from "./chunk-2EKH4A4V.js";
36
41
 
37
42
  // src/config.ts
38
43
  import path from "path";
@@ -149,6 +154,7 @@ async function scanContent(projectRoot, config) {
149
154
  const markdown = [];
150
155
  const assets = [];
151
156
  for (const relativePath of entries.sort()) {
157
+ const sourcePath = relativePath.replace(/\\/g, "/");
152
158
  const absolutePath = path2.join(contentRoot, relativePath);
153
159
  const stats = await fs.lstat(absolutePath);
154
160
  if (!stats.isFile()) continue;
@@ -158,7 +164,7 @@ async function scanContent(projectRoot, config) {
158
164
  const parsed = matter(raw);
159
165
  markdown.push({
160
166
  absolutePath,
161
- relativePath: relativePath.replace(/\\/g, "/"),
167
+ sourcePath,
162
168
  slug: slugifyFilePath(
163
169
  asFilePath(relativePath),
164
170
  config.contentDir,
@@ -175,12 +181,26 @@ async function scanContent(projectRoot, config) {
175
181
  } else {
176
182
  assets.push({
177
183
  absolutePath,
178
- relativePath: relativePath.replace(/\\/g, "/")
184
+ sourcePath,
185
+ assetPath: slugifyAssetPath(sourcePath, config.ordering)
179
186
  });
180
187
  }
181
188
  }
189
+ assertUniqueAssetPaths(assets);
182
190
  return { markdown, assets };
183
191
  }
192
+ function assertUniqueAssetPaths(assets) {
193
+ const sourcePathByAssetPath = /* @__PURE__ */ new Map();
194
+ for (const asset of assets) {
195
+ const existing = sourcePathByAssetPath.get(asset.assetPath);
196
+ if (existing && existing !== asset.sourcePath) {
197
+ throw new Error(
198
+ `Asset path collision: ${existing} and ${asset.sourcePath} both map to ${asset.assetPath}`
199
+ );
200
+ }
201
+ sourcePathByAssetPath.set(asset.assetPath, asset.sourcePath);
202
+ }
203
+ }
184
204
  async function isWithinRoot(absolutePath, realRoot) {
185
205
  const realPath = await fs.realpath(absolutePath);
186
206
  const relative = path2.relative(realRoot, realPath);
@@ -208,7 +228,7 @@ import {
208
228
  makeExcerpt
209
229
  } from "@silicajs/search";
210
230
  var VAULT_DATABASE_FILENAME = "vault.db";
211
- var VAULT_DATABASE_VERSION = "1";
231
+ var VAULT_DATABASE_VERSION = "2";
212
232
  async function writeVaultDatabase(projectRoot, input) {
213
233
  const silicaRoot = path3.join(projectRoot, ".silica");
214
234
  await fs2.ensureDir(silicaRoot);
@@ -241,7 +261,7 @@ function createVaultDatabaseSchema(db) {
241
261
  CREATE TABLE notes (
242
262
  slug TEXT PRIMARY KEY,
243
263
  file TEXT NOT NULL,
244
- relative_file TEXT NOT NULL,
264
+ source_path TEXT NOT NULL,
245
265
  title TEXT NOT NULL,
246
266
  menu_label TEXT NOT NULL,
247
267
  description TEXT,
@@ -285,6 +305,14 @@ function createVaultDatabaseSchema(db) {
285
305
  PRIMARY KEY (strategy_key, alias, slug)
286
306
  );
287
307
 
308
+ CREATE TABLE asset_aliases (
309
+ strategy_key TEXT NOT NULL,
310
+ alias TEXT NOT NULL,
311
+ asset_path TEXT NOT NULL,
312
+ sort_key TEXT,
313
+ PRIMARY KEY (strategy_key, alias, asset_path)
314
+ );
315
+
288
316
  CREATE INDEX notes_prerender_idx ON notes(prerender, slug);
289
317
  CREATE INDEX notes_listed_sort_idx ON notes(listed, sort_key, slug);
290
318
  CREATE INDEX links_target_idx ON links(target_slug, kind, source_slug);
@@ -292,6 +320,8 @@ function createVaultDatabaseSchema(db) {
292
320
  CREATE INDEX note_tags_tag_idx ON note_tags(tag, slug);
293
321
  CREATE INDEX slug_aliases_lookup_idx
294
322
  ON slug_aliases(strategy_key, alias, sort_key, slug);
323
+ CREATE INDEX asset_aliases_lookup_idx
324
+ ON asset_aliases(strategy_key, alias, sort_key, asset_path);
295
325
  `);
296
326
  }
297
327
  function populateVaultDatabase(db, input) {
@@ -306,7 +336,7 @@ function populateVaultDatabase(db, input) {
306
336
  INSERT INTO notes (
307
337
  slug,
308
338
  file,
309
- relative_file,
339
+ source_path,
310
340
  title,
311
341
  menu_label,
312
342
  description,
@@ -325,7 +355,7 @@ function populateVaultDatabase(db, input) {
325
355
  VALUES (
326
356
  @slug,
327
357
  @file,
328
- @relativeFile,
358
+ @sourcePath,
329
359
  @title,
330
360
  @menuLabel,
331
361
  @description,
@@ -356,6 +386,15 @@ function populateVaultDatabase(db, input) {
356
386
  INSERT OR IGNORE INTO slug_aliases (strategy_key, alias, slug, sort_key)
357
387
  VALUES (?, ?, ?, ?)
358
388
  `);
389
+ const insertAssetAlias = db.prepare(`
390
+ INSERT OR IGNORE INTO asset_aliases (
391
+ strategy_key,
392
+ alias,
393
+ asset_path,
394
+ sort_key
395
+ )
396
+ VALUES (?, ?, ?, ?)
397
+ `);
359
398
  const insertAll = db.transaction(() => {
360
399
  insertMetadata.run("version", VAULT_DATABASE_VERSION);
361
400
  insertMetadata.run("generatedAt", input.manifest.generatedAt);
@@ -375,7 +414,7 @@ function populateVaultDatabase(db, input) {
375
414
  insertNote.run({
376
415
  slug: entry.slug,
377
416
  file: entry.file,
378
- relativeFile: entry.relativeFile,
417
+ sourcePath: entry.sourcePath,
379
418
  title: entry.title,
380
419
  menuLabel: entry.menuLabel,
381
420
  description: entry.description,
@@ -423,6 +462,14 @@ function populateVaultDatabase(db, input) {
423
462
  );
424
463
  }
425
464
  }
465
+ for (const asset of input.assets) {
466
+ for (const [strategy, alias] of makeAssetAliases(
467
+ asset.sourcePath,
468
+ input.config.ordering
469
+ )) {
470
+ insertAssetAlias.run(strategy, alias, asset.assetPath, asset.assetPath);
471
+ }
472
+ }
426
473
  });
427
474
  insertAll();
428
475
  buildSearchTables(db, input.searchRecords);
@@ -447,6 +494,20 @@ function makeSlugAliases(slug) {
447
494
  alias
448
495
  ]);
449
496
  }
497
+ function makeAssetAliases(sourcePath, ordering) {
498
+ const aliases = /* @__PURE__ */ new Map();
499
+ const normalized = normalizeAssetReference(sourcePath, ordering);
500
+ const basename = normalizeAssetReference(
501
+ path3.posix.basename(sourcePath),
502
+ ordering
503
+ );
504
+ if (normalized) aliases.set(`absolute:${normalized}`, normalized);
505
+ if (basename) aliases.set(`shortest:${basename}`, basename);
506
+ return [...aliases.entries()].map(([key, alias]) => [
507
+ key.split(":")[0] ?? "shortest",
508
+ alias
509
+ ]);
510
+ }
450
511
  async function removeDatabaseFiles(databasePath) {
451
512
  await fs2.remove(databasePath);
452
513
  await removeDatabaseSidecars(databasePath);
@@ -470,18 +531,23 @@ async function precompute(options = {}) {
470
531
  const config = options.config ?? await loadConfig(projectRoot);
471
532
  const scan = await scanContent(projectRoot, config);
472
533
  const markdownFiles = filterPublished(scan.markdown, config);
534
+ const assetEntries = scan.assets.map(({ sourcePath, assetPath }) => ({
535
+ sourcePath,
536
+ assetPath
537
+ }));
473
538
  const allSlugs = markdownFiles.map((file) => file.slug);
474
539
  const wikilinkIndex = createWikiLinkResolutionIndex(
475
540
  allSlugs,
476
541
  config.ordering
477
542
  );
543
+ const assetIndex = createAssetResolutionIndex(assetEntries, config.ordering);
478
544
  const entries = [];
479
545
  const graphLinks = {};
480
546
  const brokenLinks = [];
481
547
  const searchRecords = [];
482
548
  const runtimeContentRoot = path4.join(projectRoot, ".silica/content");
483
549
  const relativeGitPaths = markdownFiles.map(
484
- (file) => normalizeGitPath(path4.join(config.contentDir, file.relativePath))
550
+ (file) => normalizeGitPath(path4.join(config.contentDir, file.sourcePath))
485
551
  );
486
552
  const gitDatesByPath = await getGitDatesForFiles(
487
553
  projectRoot,
@@ -492,16 +558,18 @@ async function precompute(options = {}) {
492
558
  await writeRuntimeMarkdown(runtimeContentRoot, markdownFiles);
493
559
  const analyses = await analyzeMarkdownFiles(markdownFiles, config, allSlugs, {
494
560
  concurrency: options.analysisConcurrency,
495
- wikilinkIndex
561
+ wikilinkIndex,
562
+ assetIndex,
563
+ assetEntries
496
564
  });
497
565
  for (const [index, file] of markdownFiles.entries()) {
498
566
  const gitDates = gitDatesByPath.get(
499
- normalizeGitPath(path4.join(config.contentDir, file.relativePath))
567
+ normalizeGitPath(path4.join(config.contentDir, file.sourcePath))
500
568
  ) ?? {};
501
569
  const analysis = analyses[index];
502
- const title = analysis.title ?? titleFromFilePath(file.relativePath, config.ordering);
570
+ const title = analysis.title ?? titleFromFilePath(file.sourcePath, config.ordering);
503
571
  const menuLabel = getMenuLabel(file.frontmatter, title);
504
- const sortKey = config.ordering.numericPrefixes && hasNumericPrefixInPath(file.relativePath) ? numericPrefixSortKey(file.relativePath) : void 0;
572
+ const sortKey = config.ordering.numericPrefixes && hasNumericPrefixInPath(file.sourcePath) ? numericPrefixSortKey(file.sourcePath) : void 0;
505
573
  const entry = {
506
574
  slug: file.slug,
507
575
  title,
@@ -509,8 +577,8 @@ async function precompute(options = {}) {
509
577
  description: analysis.description,
510
578
  generatedDescription: analysis.generatedDescription,
511
579
  tags: analysis.tags,
512
- file: normalizeGitPath(path4.join(".silica/content", file.relativePath)),
513
- relativeFile: file.relativePath,
580
+ file: normalizeGitPath(path4.join(".silica/content", file.sourcePath)),
581
+ sourcePath: file.sourcePath,
514
582
  sortKey,
515
583
  created: stringifyDate(
516
584
  getDate(file.frontmatter.created) ?? getDate(file.frontmatter.date) ?? gitDates.created ?? file.stats.birthtime
@@ -549,7 +617,8 @@ async function precompute(options = {}) {
549
617
  renderHashes,
550
618
  cacheState,
551
619
  prerender,
552
- searchRecords
620
+ searchRecords,
621
+ assets: assetEntries
553
622
  });
554
623
  await writeSitemapAndRobots(projectRoot, config, manifest);
555
624
  if (config.wikilinks.strict && brokenLinks.length > 0) {
@@ -569,17 +638,30 @@ ${message}`);
569
638
  async function analyzeMarkdownFiles(files, config, allSlugs, options) {
570
639
  const workerCount = getAnalysisWorkerCount(files.length, options.concurrency);
571
640
  if (workerCount <= 1) {
572
- return analyzeMarkdownFilesSerial(files, config, options.wikilinkIndex);
641
+ return analyzeMarkdownFilesSerial(
642
+ files,
643
+ config,
644
+ options.wikilinkIndex,
645
+ options.assetIndex
646
+ );
573
647
  }
574
- return analyzeMarkdownFilesParallel(files, config, allSlugs, workerCount);
648
+ return analyzeMarkdownFilesParallel(
649
+ files,
650
+ config,
651
+ allSlugs,
652
+ options.assetEntries,
653
+ workerCount
654
+ );
575
655
  }
576
- async function analyzeMarkdownFilesSerial(files, config, wikilinkIndex) {
656
+ async function analyzeMarkdownFilesSerial(files, config, wikilinkIndex, assetIndex) {
577
657
  const analyses = [];
578
658
  for (const file of files) {
579
659
  analyses.push(
580
660
  await analyzeMarkdown(file.raw, {
581
661
  slug: asFullSlug(file.slug),
662
+ sourcePath: file.sourcePath,
582
663
  wikilinkIndex,
664
+ assetIndex,
583
665
  assetBaseUrl: "/silica",
584
666
  wikilinkStrategy: config.wikilinks.strategy,
585
667
  tags: config.tags,
@@ -589,7 +671,7 @@ async function analyzeMarkdownFilesSerial(files, config, wikilinkIndex) {
589
671
  }
590
672
  return analyses;
591
673
  }
592
- function analyzeMarkdownFilesParallel(files, config, allSlugs, workerCount) {
674
+ function analyzeMarkdownFilesParallel(files, config, allSlugs, assetEntries, workerCount) {
593
675
  return new Promise((resolve, reject) => {
594
676
  const workerUrl = new URL("./precompute-worker.js", import.meta.url);
595
677
  const workers = [];
@@ -619,6 +701,7 @@ function analyzeMarkdownFilesParallel(files, config, allSlugs, workerCount) {
619
701
  const batch = files.slice(start, start + ANALYSIS_BATCH_SIZE).map((file, offset) => ({
620
702
  index: start + offset,
621
703
  slug: file.slug,
704
+ sourcePath: file.sourcePath,
622
705
  raw: file.raw
623
706
  }));
624
707
  nextIndex += batch.length;
@@ -631,6 +714,7 @@ function analyzeMarkdownFilesParallel(files, config, allSlugs, workerCount) {
631
714
  const worker = new Worker(workerUrl, {
632
715
  workerData: {
633
716
  allSlugs,
717
+ assetEntries,
634
718
  wikilinkStrategy: config.wikilinks.strategy,
635
719
  tags: config.tags,
636
720
  ordering: config.ordering
@@ -687,7 +771,7 @@ function getRequestedAnalysisConcurrency(requestedConcurrency, available) {
687
771
  async function writeRuntimeMarkdown(runtimeContentRoot, files) {
688
772
  await fs3.emptyDir(runtimeContentRoot);
689
773
  for (const file of files) {
690
- const destination = path4.join(runtimeContentRoot, file.relativePath);
774
+ const destination = path4.join(runtimeContentRoot, file.sourcePath);
691
775
  await fs3.ensureDir(path4.dirname(destination));
692
776
  await fs3.writeFile(destination, file.raw);
693
777
  }
@@ -764,7 +848,7 @@ function makeRenderHashes(manifest, graph) {
764
848
  description: entry.description,
765
849
  generatedDescription: entry.generatedDescription,
766
850
  tags: entry.tags,
767
- relativeFile: entry.relativeFile,
851
+ sourcePath: entry.sourcePath,
768
852
  sortKey: entry.sortKey,
769
853
  created: entry.created,
770
854
  modified: entry.modified,
@@ -974,11 +1058,11 @@ async function copyAssets(projectRoot, config, assets) {
974
1058
  await fs3.emptyDir(destinationRoot);
975
1059
  for (const asset of assets) {
976
1060
  await fs3.ensureDir(
977
- path4.dirname(path4.join(destinationRoot, asset.relativePath))
1061
+ path4.dirname(path4.join(destinationRoot, asset.assetPath))
978
1062
  );
979
1063
  await fs3.copyFile(
980
1064
  asset.absolutePath,
981
- path4.join(destinationRoot, asset.relativePath)
1065
+ path4.join(destinationRoot, asset.assetPath)
982
1066
  );
983
1067
  }
984
1068
  await fs3.ensureDir(path4.join(projectRoot, ".silica/next/public"));
@@ -1039,6 +1123,7 @@ export {
1039
1123
  asFullSlug,
1040
1124
  asRelativeURL,
1041
1125
  asSimpleSlug,
1126
+ createAssetResolutionIndex,
1042
1127
  createWikiLinkResolutionIndex,
1043
1128
  defineConfig,
1044
1129
  formatPropertyLabel,
@@ -1054,19 +1139,23 @@ export {
1054
1139
  isMarkdownFile,
1055
1140
  joinSegments,
1056
1141
  loadConfig,
1142
+ normalizeAssetReference,
1057
1143
  normalizePath,
1058
1144
  normalizeSlug,
1059
1145
  pathToRoot,
1060
1146
  precompute,
1061
1147
  renderMarkdown,
1062
1148
  renderMarkdownHtml,
1149
+ resolveAssetPath,
1063
1150
  resolveConfig,
1064
1151
  resolvePublicAssetPath,
1065
1152
  resolveRelative,
1153
+ resolveRelativeAsset,
1066
1154
  resolveWikiLink,
1067
1155
  scanContent,
1068
1156
  simplifySlug,
1069
1157
  slugToHref,
1158
+ slugifyAssetPath,
1070
1159
  slugifyFilePath,
1071
1160
  slugifySegment,
1072
1161
  tagToHref