@nuasite/cli 0.41.0 → 0.42.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/dist/index.js CHANGED
@@ -22551,6 +22551,7 @@ function parseConfigSource(source, sourcePath) {
22551
22551
  const bindings = new Map;
22552
22552
  const collectionDecls = new Map;
22553
22553
  const exportMap = new Map;
22554
+ const inlineCollections = new Map;
22554
22555
  for (const stmt of ast.program.body) {
22555
22556
  const varDecl = stmt.type === "ExportNamedDeclaration" && stmt.declaration?.type === "VariableDeclaration" ? stmt.declaration : stmt.type === "VariableDeclaration" ? stmt : null;
22556
22557
  if (!varDecl)
@@ -22570,6 +22571,11 @@ function parseConfigSource(source, sourcePath) {
22570
22571
  continue;
22571
22572
  if (prop.value.type === "Identifier") {
22572
22573
  exportMap.set(prop.value.name, key);
22574
+ } else if (prop.value.type === "CallExpression" && isDefineCollectionCallee(prop.value.callee)) {
22575
+ const inlineArg = prop.value.arguments[0];
22576
+ if (inlineArg?.type === "ObjectExpression") {
22577
+ inlineCollections.set(key, inlineArg);
22578
+ }
22573
22579
  }
22574
22580
  }
22575
22581
  continue;
@@ -22582,19 +22588,46 @@ function parseConfigSource(source, sourcePath) {
22582
22588
  }
22583
22589
  }
22584
22590
  }
22591
+ const collectionObjects = new Map(inlineCollections);
22585
22592
  for (const [varName, collectionName] of exportMap) {
22586
22593
  const decl = collectionDecls.get(varName);
22587
- if (!decl)
22588
- continue;
22594
+ if (decl)
22595
+ collectionObjects.set(collectionName, decl);
22596
+ }
22597
+ for (const [collectionName, decl] of collectionObjects) {
22598
+ const loaderProperty = decl.properties.find((p) => p.type === "ObjectProperty" && propertyKeyName(p.key) === "loader");
22599
+ const loaderOptions = loaderProperty ? extractGlobLoaderOptions(loaderProperty.value, bindings) : {};
22600
+ const loaderPattern = loaderOptions.pattern;
22601
+ const loaderBase = loaderOptions.base;
22589
22602
  const schemaProperty = decl.properties.find((p) => p.type === "ObjectProperty" && propertyKeyName(p.key) === "schema");
22590
- if (!schemaProperty)
22603
+ if (!schemaProperty) {
22604
+ if (!loaderPattern)
22605
+ continue;
22606
+ result.set(collectionName, {
22607
+ name: collectionName,
22608
+ fields: [],
22609
+ loaderPattern,
22610
+ loaderBase
22611
+ });
22591
22612
  continue;
22613
+ }
22592
22614
  const schemaObject = unwrapSchemaToObject(schemaProperty.value, bindings);
22593
- if (!schemaObject)
22615
+ if (!schemaObject) {
22616
+ if (!loaderPattern)
22617
+ continue;
22618
+ result.set(collectionName, {
22619
+ name: collectionName,
22620
+ fields: [],
22621
+ loaderPattern,
22622
+ loaderBase
22623
+ });
22594
22624
  continue;
22625
+ }
22595
22626
  result.set(collectionName, {
22596
22627
  name: collectionName,
22597
- fields: parseSchemaFields(schemaObject, bindings)
22628
+ fields: parseSchemaFields(schemaObject, bindings),
22629
+ loaderPattern,
22630
+ loaderBase
22598
22631
  });
22599
22632
  }
22600
22633
  return result;
@@ -22609,6 +22642,43 @@ function propertyKeyName(key) {
22609
22642
  return key.value;
22610
22643
  return null;
22611
22644
  }
22645
+ function extractGlobLoaderOptions(node, bindings) {
22646
+ const resolved = resolveExpression(node, bindings);
22647
+ if (resolved.type !== "CallExpression")
22648
+ return {};
22649
+ if (!isGlobCallee(resolved.callee))
22650
+ return {};
22651
+ const arg = resolved.arguments[0];
22652
+ if (!arg)
22653
+ return {};
22654
+ const options = resolveExpression(arg, bindings);
22655
+ if (options.type !== "ObjectExpression")
22656
+ return {};
22657
+ const result = {};
22658
+ for (const prop of options.properties) {
22659
+ if (prop.type !== "ObjectProperty")
22660
+ continue;
22661
+ const key = propertyKeyName(prop.key);
22662
+ if (key !== "pattern" && key !== "base")
22663
+ continue;
22664
+ const value = extractStaticString(prop.value, bindings);
22665
+ if (value !== undefined)
22666
+ result[key] = value;
22667
+ }
22668
+ return result;
22669
+ }
22670
+ function extractStaticString(node, bindings) {
22671
+ const resolved = resolveExpression(node, bindings);
22672
+ if (resolved.type === "StringLiteral")
22673
+ return resolved.value;
22674
+ if (resolved.type === "TemplateLiteral" && resolved.expressions.length === 0) {
22675
+ return resolved.quasis[0]?.value.cooked ?? resolved.quasis[0]?.value.raw;
22676
+ }
22677
+ return;
22678
+ }
22679
+ function isGlobCallee(callee) {
22680
+ return callee.type === "Identifier" && callee.name === "glob";
22681
+ }
22612
22682
  function unwrapSchemaToObject(node, bindings) {
22613
22683
  const resolved = resolveExpression(node, bindings);
22614
22684
  if (resolved.type === "ArrowFunctionExpression" || resolved.type === "FunctionExpression") {
@@ -23013,7 +23083,7 @@ function collectFieldObservations(fieldMap, data, totalEntries) {
23013
23083
  obs.presentCount++;
23014
23084
  }
23015
23085
  }
23016
- function buildCollectionDefinition(collectionName, contentDir, fieldMap, entryInfos, entryCount, extra) {
23086
+ function assembleCollectionDefinition(collectionName, contentDir, fieldMap, entryInfos, entryCount, extra) {
23017
23087
  for (const obs of fieldMap.values()) {
23018
23088
  obs.totalEntries = entryCount;
23019
23089
  }
@@ -23031,6 +23101,68 @@ function buildCollectionDefinition(collectionName, contentDir, fieldMap, entryIn
23031
23101
  ...extra
23032
23102
  };
23033
23103
  }
23104
+ function getCollectionSourceBasePath(basePath, collectionName, contentDir) {
23105
+ const projectRoot = getProjectRoot();
23106
+ const defaultContentDir = path2.isAbsolute(contentDir) ? contentDir : path2.join(projectRoot, contentDir);
23107
+ const defaultCollectionPath = path2.join(defaultContentDir, collectionName);
23108
+ if (path2.resolve(basePath) === path2.resolve(defaultCollectionPath)) {
23109
+ return path2.join(contentDir, collectionName);
23110
+ }
23111
+ const relativeBase = path2.relative(projectRoot, basePath);
23112
+ if (relativeBase && !relativeBase.startsWith("..") && !path2.isAbsolute(relativeBase)) {
23113
+ return relativeBase;
23114
+ }
23115
+ return basePath;
23116
+ }
23117
+ async function buildCollectionDefinition(basePath, sources, collectionName, contentDir) {
23118
+ if (sources.length === 0)
23119
+ return null;
23120
+ const sourceBasePath = getCollectionSourceBasePath(basePath, collectionName, contentDir);
23121
+ const hasMd = sources.some((s) => s.relPath.endsWith(".md"));
23122
+ const fileExtension = hasMd ? "md" : "mdx";
23123
+ const fieldMap = new Map;
23124
+ const allDirectives = {};
23125
+ const entryInfos = [];
23126
+ let hasDraft = false;
23127
+ const fileContents = await Promise.all(sources.map((s) => fs2.readFile(path2.join(basePath, s.relPath), "utf-8")));
23128
+ for (let i = 0;i < sources.length; i++) {
23129
+ const source = sources[i];
23130
+ const content = fileContents[i];
23131
+ const frontmatter = parseFrontmatter2(content);
23132
+ const directives = parseFieldDirectives(content);
23133
+ for (const [key, value] of Object.entries(directives)) {
23134
+ if (!allDirectives[key]) {
23135
+ allDirectives[key] = value;
23136
+ }
23137
+ }
23138
+ const entryInfo = {
23139
+ slug: source.slug,
23140
+ sourcePath: path2.join(sourceBasePath, source.relPath)
23141
+ };
23142
+ if (frontmatter) {
23143
+ if (typeof frontmatter.title === "string") {
23144
+ entryInfo.title = frontmatter.title;
23145
+ }
23146
+ if (typeof frontmatter.draft === "boolean" && frontmatter.draft) {
23147
+ entryInfo.draft = true;
23148
+ }
23149
+ entryInfo.data = frontmatter;
23150
+ }
23151
+ entryInfos.push(entryInfo);
23152
+ if (!frontmatter)
23153
+ continue;
23154
+ if (frontmatter.draft === true)
23155
+ hasDraft = true;
23156
+ collectFieldObservations(fieldMap, frontmatter, sources.length);
23157
+ }
23158
+ const def = assembleCollectionDefinition(collectionName, contentDir, fieldMap, entryInfos, sources.length, {
23159
+ path: sourceBasePath,
23160
+ supportsDraft: hasDraft,
23161
+ fileExtension
23162
+ });
23163
+ assignFieldMetadata(def.fields, allDirectives);
23164
+ return def;
23165
+ }
23034
23166
  async function scanCollection(collectionPath, collectionName, contentDir) {
23035
23167
  try {
23036
23168
  const dirEntries = await fs2.readdir(collectionPath, { withFileTypes: true });
@@ -23064,49 +23196,71 @@ async function scanCollection(collectionPath, collectionName, contentDir) {
23064
23196
  }
23065
23197
  if (sources.length === 0)
23066
23198
  return null;
23067
- const hasMd = sources.some((s) => s.relPath.endsWith(".md"));
23068
- const fileExtension = hasMd ? "md" : "mdx";
23069
- const fieldMap = new Map;
23070
- const allDirectives = {};
23071
- const entryInfos = [];
23072
- let hasDraft = false;
23073
- const fileContents = await Promise.all(sources.map((s) => fs2.readFile(path2.join(collectionPath, s.relPath), "utf-8")));
23074
- for (let i = 0;i < sources.length; i++) {
23075
- const source = sources[i];
23076
- const content = fileContents[i];
23077
- const frontmatter = parseFrontmatter2(content);
23078
- const directives = parseFieldDirectives(content);
23079
- for (const [key, value] of Object.entries(directives)) {
23080
- if (!allDirectives[key]) {
23081
- allDirectives[key] = value;
23082
- }
23199
+ return await buildCollectionDefinition(collectionPath, sources, collectionName, contentDir);
23200
+ } catch {
23201
+ return null;
23202
+ }
23203
+ }
23204
+ function globToRegExp(glob) {
23205
+ let re = "";
23206
+ for (let i = 0;i < glob.length; i++) {
23207
+ const c = glob[i];
23208
+ if (c === "*") {
23209
+ if (glob[i + 1] === "*") {
23210
+ re += ".*";
23211
+ i++;
23212
+ if (glob[i + 1] === "/")
23213
+ i++;
23214
+ } else {
23215
+ re += "[^/]*";
23083
23216
  }
23084
- const entryInfo = {
23085
- slug: source.slug,
23086
- sourcePath: path2.join(contentDir, collectionName, source.relPath)
23087
- };
23088
- if (frontmatter) {
23089
- if (typeof frontmatter.title === "string") {
23090
- entryInfo.title = frontmatter.title;
23091
- }
23092
- if (typeof frontmatter.draft === "boolean" && frontmatter.draft) {
23093
- entryInfo.draft = true;
23094
- }
23095
- entryInfo.data = frontmatter;
23217
+ } else if (c === "?") {
23218
+ re += "[^/]";
23219
+ } else if (c === "{") {
23220
+ const end = glob.indexOf("}", i);
23221
+ if (end === -1) {
23222
+ re += "\\{";
23223
+ } else {
23224
+ const opts = glob.slice(i + 1, end).split(",").map((s) => s.replace(/[.+^${}()|[\]\\]/g, "\\$&"));
23225
+ re += `(?:${opts.join("|")})`;
23226
+ i = end;
23096
23227
  }
23097
- entryInfos.push(entryInfo);
23098
- if (!frontmatter)
23099
- continue;
23100
- if (frontmatter.draft === true)
23101
- hasDraft = true;
23102
- collectFieldObservations(fieldMap, frontmatter, sources.length);
23228
+ } else if (".+^$()|[]\\".includes(c)) {
23229
+ re += `\\${c}`;
23230
+ } else {
23231
+ re += c;
23103
23232
  }
23104
- const def = buildCollectionDefinition(collectionName, contentDir, fieldMap, entryInfos, sources.length, {
23105
- supportsDraft: hasDraft,
23106
- fileExtension
23107
- });
23108
- assignFieldMetadata(def.fields, allDirectives);
23109
- return def;
23233
+ }
23234
+ return new RegExp(`^${re}$`);
23235
+ }
23236
+ async function walkFiles(dir, prefix = "") {
23237
+ let dirEntries;
23238
+ try {
23239
+ dirEntries = await fs2.readdir(dir, { withFileTypes: true });
23240
+ } catch {
23241
+ return [];
23242
+ }
23243
+ const out = [];
23244
+ for (const entry of dirEntries) {
23245
+ if (entry.name.startsWith("_") || entry.name.startsWith("."))
23246
+ continue;
23247
+ const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
23248
+ if (entry.isDirectory()) {
23249
+ out.push(...await walkFiles(path2.join(dir, entry.name), rel));
23250
+ } else if (entry.isFile()) {
23251
+ out.push(rel);
23252
+ }
23253
+ }
23254
+ return out;
23255
+ }
23256
+ async function scanGlobCollection(collectionName, baseRel, pattern, contentDir) {
23257
+ try {
23258
+ const absBase = path2.join(getProjectRoot(), baseRel);
23259
+ const matcher = globToRegExp(pattern);
23260
+ const sources = (await walkFiles(absBase)).filter((rel) => (rel.endsWith(".md") || rel.endsWith(".mdx")) && matcher.test(rel)).map((rel) => ({ slug: rel.replace(/\.(md|mdx)$/, ""), relPath: rel }));
23261
+ if (sources.length === 0)
23262
+ return null;
23263
+ return await buildCollectionDefinition(absBase, sources, collectionName, contentDir);
23110
23264
  } catch {
23111
23265
  return null;
23112
23266
  }
@@ -23422,7 +23576,7 @@ async function scanDataCollection(collectionPath, collectionName, contentDir) {
23422
23576
  });
23423
23577
  collectFieldObservations(fieldMap, data, sources.length);
23424
23578
  }
23425
- return buildCollectionDefinition(collectionName, contentDir, fieldMap, entryInfos, sources.length, {
23579
+ return assembleCollectionDefinition(collectionName, contentDir, fieldMap, entryInfos, sources.length, {
23426
23580
  type: "data",
23427
23581
  fileExtension: ext
23428
23582
  });
@@ -23446,6 +23600,20 @@ async function scanCollections(contentDir = "src/content") {
23446
23600
  await Promise.all(scanPromises);
23447
23601
  } catch {}
23448
23602
  const parsed = await parseContentConfig();
23603
+ for (const [collectionName, parsedCollection] of parsed) {
23604
+ if (collections[collectionName])
23605
+ continue;
23606
+ if (!parsedCollection.loaderBase || !parsedCollection.loaderPattern)
23607
+ continue;
23608
+ const definition = await scanGlobCollection(collectionName, parsedCollection.loaderBase, parsedCollection.loaderPattern, contentDir);
23609
+ if (!definition)
23610
+ continue;
23611
+ const baseName = parsedCollection.loaderBase.replace(/[/\\]+$/, "").split(/[/\\]/).pop();
23612
+ if (baseName && baseName !== collectionName && collections[baseName]) {
23613
+ definition.parentCollection = baseName;
23614
+ }
23615
+ collections[collectionName] = definition;
23616
+ }
23449
23617
  applyParsedConfig(collections, parsed);
23450
23618
  detectReferenceFields(collections, parsed);
23451
23619
  detectDerivedHrefFields(collections);
@@ -24830,7 +24998,8 @@ async function handleCreateMarkdown(request) {
24830
24998
  return { success: false, error: `Invalid file extension "${ext}". Allowed: ${allowedExtensions.join(", ")}` };
24831
24999
  }
24832
25000
  const isData = ext === "json" || ext === "yaml" || ext === "yml";
24833
- const filePath = `src/content/${collection}/${normalizedSlug}.${ext}`;
25001
+ const layout = isData ? "flat" : await detectCollectionMarkdownLayout(collection);
25002
+ const filePath = layout === "index" ? `src/content/${collection}/${normalizedSlug}/index.${ext}` : `src/content/${collection}/${normalizedSlug}.${ext}`;
24834
25003
  const fullPath = resolveAndValidatePath(filePath);
24835
25004
  let fileContent;
24836
25005
  if (isData) {
@@ -24918,6 +25087,68 @@ async function handleRenameMarkdown(request) {
24918
25087
  function isDataFile(filePath) {
24919
25088
  return filePath.endsWith(".json") || filePath.endsWith(".yaml") || filePath.endsWith(".yml");
24920
25089
  }
25090
+ async function detectCollectionMarkdownLayout(collection) {
25091
+ const existingLayout = await inferLayoutFromExistingEntries(collection);
25092
+ if (existingLayout)
25093
+ return existingLayout;
25094
+ const configLayout = await inferLayoutFromContentConfig(collection);
25095
+ if (configLayout)
25096
+ return configLayout;
25097
+ return "flat";
25098
+ }
25099
+ async function inferLayoutFromExistingEntries(collection) {
25100
+ const collectionPath = path9.join(getProjectRoot(), "src", "content", collection);
25101
+ let dirEntries;
25102
+ try {
25103
+ dirEntries = await fs9.readdir(collectionPath, { withFileTypes: true });
25104
+ } catch {
25105
+ return null;
25106
+ }
25107
+ let flatCount = 0;
25108
+ let indexCount = 0;
25109
+ const flatSlugs = new Set;
25110
+ for (const entry of dirEntries) {
25111
+ if (!entry.isFile())
25112
+ continue;
25113
+ const match = entry.name.match(/^(.+)\.(md|mdx)$/);
25114
+ if (!match)
25115
+ continue;
25116
+ flatCount++;
25117
+ flatSlugs.add(match[1]);
25118
+ }
25119
+ const subdirs = dirEntries.filter((entry) => entry.isDirectory() && !entry.name.startsWith("_") && !entry.name.startsWith("."));
25120
+ const indexLookups = await Promise.all(subdirs.map(async (dir) => {
25121
+ if (flatSlugs.has(dir.name))
25122
+ return false;
25123
+ for (const ext of ["md", "mdx"]) {
25124
+ try {
25125
+ await fs9.access(path9.join(collectionPath, dir.name, `index.${ext}`));
25126
+ return true;
25127
+ } catch {}
25128
+ }
25129
+ return false;
25130
+ }));
25131
+ indexCount = indexLookups.filter(Boolean).length;
25132
+ if (indexCount > flatCount)
25133
+ return "index";
25134
+ if (flatCount > 0)
25135
+ return "flat";
25136
+ return null;
25137
+ }
25138
+ async function inferLayoutFromContentConfig(collection) {
25139
+ try {
25140
+ const parsed = await parseContentConfig();
25141
+ const pattern = parsed.get(collection)?.loaderPattern;
25142
+ if (!pattern)
25143
+ return null;
25144
+ return isIndexStyleGlobPattern(pattern) ? "index" : "flat";
25145
+ } catch {
25146
+ return null;
25147
+ }
25148
+ }
25149
+ function isIndexStyleGlobPattern(pattern) {
25150
+ return pattern.includes("index.{") || pattern.includes("*/index") || pattern.includes("**/index");
25151
+ }
24921
25152
  function parseFrontmatter3(raw) {
24922
25153
  const trimmed = raw.trimStart();
24923
25154
  if (!trimmed.startsWith("---")) {
@@ -25027,6 +25258,7 @@ function ensureMdxImports(content, filePath, componentDefinitions) {
25027
25258
  var import_yaml2, YAML_DATE_PATTERN;
25028
25259
  var init_markdown_ops = __esm(() => {
25029
25260
  init_config();
25261
+ init_content_config_ast();
25030
25262
  init_utils2();
25031
25263
  import_yaml2 = __toESM(require_dist(), 1);
25032
25264
  YAML_DATE_PATTERN = /^\d{4}-\d{2}-\d{2}/;
@@ -32787,6 +33019,7 @@ var init_tailwind_colors = __esm(() => {
32787
33019
 
32788
33020
  // ../cms/src/html-processor.ts
32789
33021
  var init_html_processor = __esm(() => {
33022
+ init_config();
32790
33023
  init_seo_processor();
32791
33024
  init_tailwind_colors();
32792
33025
  init_utils2();