eidosmd 0.1.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.
Files changed (107) hide show
  1. package/README.md +50 -29
  2. package/browser/dist/assets/index-Cc3cNWHY.css +1 -0
  3. package/browser/dist/assets/index-DQgCQRa5.js +46 -0
  4. package/browser/dist/favicon.svg +5 -0
  5. package/browser/dist/index.html +15 -0
  6. package/browser/dist/mark.svg +4 -0
  7. package/dist/src/cli.js +7 -0
  8. package/dist/src/commands/agents.js +1 -1
  9. package/dist/src/commands/canvas.js +77 -0
  10. package/dist/src/commands/check.js +10 -4
  11. package/dist/src/commands/configure.js +201 -0
  12. package/dist/src/commands/framework.js +37 -7
  13. package/dist/src/commands/index.js +4 -4
  14. package/dist/src/commands/init.js +5 -0
  15. package/dist/src/commands/instructions.js +1 -1
  16. package/dist/src/commands/list.js +8 -8
  17. package/dist/src/commands/migrate.js +32 -0
  18. package/dist/src/commands/new.js +3 -3
  19. package/dist/src/commands/property.js +125 -0
  20. package/dist/src/commands/seeds.js +5 -5
  21. package/dist/src/commands/setup.js +131 -0
  22. package/dist/src/commands/version.js +44 -0
  23. package/dist/src/commands/whoami.js +4 -4
  24. package/dist/src/context.js +5 -5
  25. package/dist/src/core/blueprint.js +16 -11
  26. package/dist/src/core/canvas-schema.js +148 -0
  27. package/dist/src/core/canvas.js +732 -0
  28. package/dist/src/core/check.js +221 -70
  29. package/dist/src/core/convert.js +7 -6
  30. package/dist/src/core/edits.js +1381 -0
  31. package/dist/src/core/framework-markdown.js +119 -34
  32. package/dist/src/core/framework-model.js +62 -10
  33. package/dist/src/core/framework-structured.js +222 -47
  34. package/dist/src/core/framework.js +13 -13
  35. package/dist/src/core/frontmatter.js +61 -1
  36. package/dist/src/core/git.js +84 -0
  37. package/dist/src/core/index-leaf.js +2 -2
  38. package/dist/src/core/links.js +87 -0
  39. package/dist/src/core/markdown.js +16 -9
  40. package/dist/src/core/me.js +16 -8
  41. package/dist/src/core/migrate.js +319 -0
  42. package/dist/src/core/naming.js +1 -1
  43. package/dist/src/core/regions.js +117 -0
  44. package/dist/src/core/root.js +2 -2
  45. package/dist/src/core/scaffold.js +33 -27
  46. package/dist/src/core/seed.js +187 -71
  47. package/dist/src/core/server.js +1410 -53
  48. package/dist/src/core/settings.js +232 -0
  49. package/dist/src/core/store.js +315 -0
  50. package/dist/src/core/template.js +32 -0
  51. package/dist/src/core/versions.js +84 -0
  52. package/dist/src/output.js +4 -1
  53. package/dist/src/program.js +421 -41
  54. package/instructions/authoring.md +15 -12
  55. package/instructions/configuring.md +81 -34
  56. package/instructions/init-required.md +4 -4
  57. package/instructions/overview.md +21 -9
  58. package/instructions/validating.md +9 -6
  59. package/package.json +21 -12
  60. package/standard/EIDOS.md +142 -259
  61. package/standard/seeds/README.md +12 -16
  62. package/standard/seeds/book/Framework.yaml +61 -0
  63. package/standard/seeds/book/README.md +10 -5
  64. package/standard/seeds/book/_gitignore +9 -3
  65. package/standard/seeds/book/me.md +1 -1
  66. package/standard/seeds/book/roles/README.md +3 -3
  67. package/standard/seeds/book/roles/framework-owner.md +2 -2
  68. package/standard/seeds/book/{shapes → templates}/chapter.full.md +0 -8
  69. package/standard/seeds/book/{shapes → templates}/chapter.sketch.md +0 -7
  70. package/standard/seeds/book/{shapes → templates}/frame.market.md +0 -6
  71. package/standard/seeds/book/templates/frame.premise.md +17 -0
  72. package/standard/seeds/book/{shapes → templates}/frame.reader.md +0 -6
  73. package/standard/seeds/book/{shapes → templates}/frame.voice.md +0 -7
  74. package/standard/seeds/research/Framework.yaml +61 -0
  75. package/standard/seeds/research/README.md +10 -5
  76. package/standard/seeds/research/_gitignore +9 -3
  77. package/standard/seeds/research/me.md +1 -1
  78. package/standard/seeds/research/roles/README.md +3 -3
  79. package/standard/seeds/research/roles/framework-owner.md +2 -2
  80. package/standard/seeds/research/{shapes → templates}/frame.ethics.md +0 -6
  81. package/standard/seeds/research/{shapes → templates}/frame.method.md +0 -7
  82. package/standard/seeds/research/{shapes → templates}/frame.prior-work.md +0 -6
  83. package/standard/seeds/research/{shapes → templates}/frame.question.md +0 -7
  84. package/standard/seeds/research/{shapes → templates}/investigation.full.md +0 -8
  85. package/standard/seeds/research/{shapes → templates}/investigation.note.md +0 -7
  86. package/standard/seeds/software/Framework.yaml +62 -0
  87. package/standard/seeds/software/README.md +7 -6
  88. package/standard/seeds/software/_gitignore +9 -3
  89. package/standard/seeds/software/me.md +1 -1
  90. package/standard/seeds/software/roles/README.md +3 -3
  91. package/standard/seeds/software/roles/framework-owner.md +2 -2
  92. package/standard/seeds/software/roles/project-manager.md +2 -2
  93. package/standard/seeds/software/roles/stakeholder.md +1 -1
  94. package/standard/seeds/software/{shapes → templates}/frame.architecture.md +0 -7
  95. package/standard/seeds/software/{shapes → templates}/frame.audience.md +1 -8
  96. package/standard/seeds/software/{shapes → templates}/frame.criteria.md +0 -8
  97. package/standard/seeds/software/{shapes → templates}/frame.market.md +0 -8
  98. package/standard/seeds/software/{shapes → templates}/spec.full.md +0 -8
  99. package/standard/seeds/software/{shapes → templates}/spec.micro.md +0 -9
  100. package/browser/index.html +0 -268
  101. package/dist/src/commands/convert.js +0 -30
  102. package/dist/src/core/shape.js +0 -26
  103. package/standard/seeds/book/Framework.md +0 -87
  104. package/standard/seeds/book/shapes/frame.premise.md +0 -24
  105. package/standard/seeds/research/Framework.md +0 -88
  106. package/standard/seeds/software/Framework.md +0 -88
  107. /package/standard/seeds/software/{shapes → templates}/.gitkeep +0 -0
@@ -1,13 +1,15 @@
1
- // The markdown framework document, `_eidos/Framework.md`: frontmatter for the
1
+ // The markdown framework document, `.eidos/Framework.md`: frontmatter for the
2
2
  // version and naming convention, and a body indexing the Top-Level docs, the
3
- // Collections (each a `###` with Leaf, Flavors, Canvas, and grouping bullets),
4
- // and the property Schema as two tables.
3
+ // Collections (each a `###` with Leaf, Variants, and grouping bullets), the
4
+ // Properties table per owner, the Vocabulary, and the Versions. The 4.x
5
+ // spellings (Schema, Flavors, shape) still read, so `migrate` can load a root
6
+ // it is about to move.
5
7
  import { parseFrontmatter } from './frontmatter.js';
6
- import { groupingProperty, STANDARD_CORE } from './framework-model.js';
8
+ import { coreRequires, groupingProperty, STANDARD_CORE, unitOf } from './framework-model.js';
7
9
  import { DEFAULT_NAMING, isNaming } from './naming.js';
8
10
  import { section, sections, stripInlineCode } from './markdown.js';
9
11
  import path from 'node:path';
10
- const DECLARED_BULLETS = new Set(['leaf', 'flavors', 'canvas']);
12
+ const DECLARED_BULLETS = new Set(['leaf', 'unit', 'variants', 'flavors', 'canvas']);
11
13
  function cleanText(text) {
12
14
  return text.replace(/^_+|_+$/g, '').trim();
13
15
  }
@@ -46,13 +48,13 @@ function parseCanvas(value) {
46
48
  }
47
49
  return { mode: 'card', section: null };
48
50
  }
49
- function flavorFromShape(label, shapeRel) {
50
- const file = path.posix.basename(shapeRel.replace(/\\/g, '/')).replace(/\.md$/i, '');
51
+ function variantFromTemplate(label, templateRel) {
52
+ const file = path.posix.basename(templateRel.replace(/\\/g, '/')).replace(/\.md$/i, '');
51
53
  const dot = file.indexOf('.');
52
- const kind = dot === -1 ? file : file.slice(0, dot);
54
+ const unit = dot === -1 ? file : file.slice(0, dot);
53
55
  const fromFile = dot === -1 ? file : file.slice(dot + 1);
54
56
  const name = /\.md$/i.test(label) ? fromFile : label;
55
- return { name, kind };
57
+ return { name, unit };
56
58
  }
57
59
  function parseCollections(body) {
58
60
  const block = section(body, 2, 'Collections');
@@ -62,22 +64,23 @@ function parseCollections(body) {
62
64
  const out = [];
63
65
  for (const { heading, body: text } of sections(block, 3)) {
64
66
  const collection = {
67
+ type: 'collection',
65
68
  name: heading.text,
66
69
  description: firstParagraph(text),
67
- framing: out.length === 0,
70
+ framing: false,
68
71
  leaf: null,
69
- flavors: [],
72
+ variants: [],
70
73
  canvas: null,
71
74
  grouping: null,
72
75
  };
73
- let inFlavors = false;
76
+ let inVariants = false;
74
77
  let inGrouping = false;
75
78
  for (const line of text.split('\n')) {
76
79
  const bullet = /^-\s+\*\*(.+?):\*\*\s*(.*)$/.exec(line);
77
80
  if (bullet && bullet[1] !== undefined) {
78
81
  const label = bullet[1].trim();
79
82
  const rest = (bullet[2] ?? '').trim();
80
- inFlavors = label.toLowerCase() === 'flavors';
83
+ inVariants = label.toLowerCase() === 'variants' || label.toLowerCase() === 'flavors';
81
84
  inGrouping = false;
82
85
  if (label.toLowerCase() === 'leaf') {
83
86
  collection.leaf = parseLink(rest)?.target ?? null;
@@ -96,18 +99,18 @@ function parseCollections(body) {
96
99
  continue;
97
100
  }
98
101
  const item = nested[1].trim();
99
- if (inFlavors) {
100
- const flavorLine = /^\[(.+?)\]\((.+?)\)\s*(?:—\s*(.*))?$/.exec(item);
101
- if (flavorLine && flavorLine[1] && flavorLine[2]) {
102
- // `(default)` in the description marks the default flavor; it is a flag, not prose.
103
- const described = (flavorLine[3] ?? '').trim();
102
+ if (inVariants) {
103
+ const variantLine = /^\[(.+?)\]\((.+?)\)\s*(?:—\s*(.*))?$/.exec(item);
104
+ if (variantLine && variantLine[1] && variantLine[2]) {
105
+ // `(default)` in the description marks the default variant; it is a flag, not prose.
106
+ const described = (variantLine[3] ?? '').trim();
104
107
  const isDefault = /\(default\)/i.test(described);
105
108
  const description = described.replace(/\s*\(default\)\.?$/i, '').replace(/\s*\(default\)/i, '').trim();
106
- const { name, kind } = flavorFromShape(flavorLine[1].trim(), flavorLine[2].trim());
107
- collection.flavors.push({
109
+ const { name, unit } = variantFromTemplate(variantLine[1].trim(), variantLine[2].trim());
110
+ collection.variants.push({
108
111
  name,
109
- kind,
110
- shape: flavorLine[2].trim(),
112
+ unit,
113
+ template: variantLine[2].trim(),
111
114
  description,
112
115
  isDefault,
113
116
  });
@@ -120,12 +123,13 @@ function parseCollections(body) {
120
123
  }
121
124
  }
122
125
  }
123
- if (collection.flavors.length > 0 && !collection.flavors.some((flavor) => flavor.isDefault)) {
124
- const first = collection.flavors[0];
126
+ if (collection.variants.length > 0 && !collection.variants.some((variant) => variant.isDefault)) {
127
+ const first = collection.variants[0];
125
128
  if (first) {
126
129
  first.isDefault = true;
127
130
  }
128
131
  }
132
+ collection.framing = unitOf(collection) === 'frame';
129
133
  out.push(collection);
130
134
  }
131
135
  return out;
@@ -152,23 +156,89 @@ function parseAppliesTo(value) {
152
156
  }
153
157
  return plain.split(/[,/]/).map((name) => name.trim()).filter((name) => name !== '');
154
158
  }
155
- function parseProperties(block, core) {
159
+ // The standard's columns come first (Name, Type, Meaning for the core; Name,
160
+ // Type, Applies To, Meaning for custom properties); any column past them is a
161
+ // tool's, headed with the tool's name, and rides along as that tool's field.
162
+ function parseProperties(block, core, owner = core ? 'eidos' : 'custom') {
156
163
  if (block === null) {
157
164
  return [];
158
165
  }
159
166
  const out = [];
167
+ const standard = core ? 3 : 4;
168
+ let header = [];
160
169
  for (const cells of parseTable(block)) {
161
170
  const name = stripInlineCode(cells[0] ?? '').trim();
162
- if (name === '' || name.toLowerCase() === 'name') {
171
+ if (name === '') {
172
+ continue;
173
+ }
174
+ if (name.toLowerCase() === 'name') {
175
+ header = cells.map((cell) => stripInlineCode(cell).trim());
163
176
  continue;
164
177
  }
165
178
  const type = (cells[1] ?? 'Text').trim();
166
- if (core || cells.length < 4) {
167
- out.push({ name, type, appliesTo: 'all', meaning: (cells[cells.length - 1] ?? '').trim(), core });
179
+ // the 4.x table had no Required column: the core requires what the
180
+ // standard says it does, and every other property is optional
181
+ const required = core && coreRequires(name);
182
+ const property = core || cells.length < 4
183
+ ? { name, type, appliesTo: 'all', required, meaning: (cells[Math.min(cells.length, standard) - 1] ?? '').trim(), core, owner }
184
+ : { name, type, appliesTo: parseAppliesTo(cells[2] ?? ''), required, meaning: (cells[3] ?? '').trim(), core, owner };
185
+ const tools = {};
186
+ for (let index = standard; index < cells.length; index += 1) {
187
+ const tool = header[index];
188
+ const cell = (cells[index] ?? '').trim();
189
+ if (tool && cell !== '') {
190
+ tools[tool] = cell;
191
+ }
168
192
  }
169
- else {
170
- out.push({ name, type, appliesTo: parseAppliesTo(cells[2] ?? ''), meaning: (cells[3] ?? '').trim(), core });
193
+ if (Object.keys(tools).length > 0) {
194
+ property.tools = tools;
171
195
  }
196
+ out.push(property);
197
+ }
198
+ return out;
199
+ }
200
+ // `## Vocabulary`: Term · Means · Not, one row per term. A Term cell may be a
201
+ // link to the blueprint that defines it; the Not cell lists near-misses, `;` apart.
202
+ function parseVocabulary(body) {
203
+ const block = section(body, 2, 'Vocabulary');
204
+ if (block === null)
205
+ return [];
206
+ const out = [];
207
+ for (const row of parseTable(block)) {
208
+ const [termCell = '', means = '', notCell = ''] = row;
209
+ const link = parseLink(termCell.trim());
210
+ const term = (link ? link.title : stripInlineCode(termCell.replace(/\*\*/g, ''))).trim();
211
+ // the header row, a placeholder row, and a blank row are not terms
212
+ if (term === '' || term.toLowerCase() === 'term' || /^<.*>$/.test(term) || /^-+$/.test(term))
213
+ continue;
214
+ out.push({
215
+ term,
216
+ means: cleanText(means),
217
+ not: notCell
218
+ .split(';')
219
+ .map((entry) => cleanText(entry))
220
+ .filter((entry) => entry !== ''),
221
+ see: link ? link.target : null,
222
+ });
223
+ }
224
+ return out;
225
+ }
226
+ // `## Versions`: Version · Commit · Tag, newest first, as written. The key
227
+ // left the standard in 5.0.0; `migrate` moves what a 4.x root recorded.
228
+ export function parseVersions(body) {
229
+ const block = section(body, 2, 'Versions');
230
+ if (block === null) {
231
+ return [];
232
+ }
233
+ const out = [];
234
+ for (const cells of parseTable(block)) {
235
+ const version = stripInlineCode(cells[0] ?? '').replace(/^\*\*|\*\*$/g, '').trim();
236
+ if (version === '' || version.toLowerCase() === 'version') {
237
+ continue;
238
+ }
239
+ const commit = stripInlineCode(cells[1] ?? '').trim();
240
+ const tag = stripInlineCode(cells[2] ?? '').trim();
241
+ out.push({ version, commit, tag: tag === '' || tag === '—' || tag === '-' ? null : tag });
172
242
  }
173
243
  return out;
174
244
  }
@@ -207,11 +277,23 @@ export function parseFrameworkMarkdown(text, root, file) {
207
277
  }
208
278
  }
209
279
  const version = properties?.['eidos_version'];
210
- const schemaBlock = section(body, 2, 'Schema') ?? '';
211
- const coreBlock = section(schemaBlock, 3, 'Eidos Core');
212
- const customBlock = section(schemaBlock, 3, 'Custom Properties');
280
+ const schemaBlock = section(body, 2, 'Properties') ?? section(body, 2, 'Schema') ?? '';
281
+ const coreBlock = section(schemaBlock, 3, 'Eidos') ?? section(schemaBlock, 3, 'Eidos Core');
282
+ const customBlock = section(schemaBlock, 3, 'Custom') ?? section(schemaBlock, 3, 'Custom Properties');
213
283
  const core = parseProperties(coreBlock, true);
214
284
  const custom = parseProperties(customBlock, false);
285
+ // `### <tool>` (`### <tool> Properties` before 5.0.0): a block a tool declared and alone writes.
286
+ const tools = {};
287
+ for (const { heading, body: block } of sections(schemaBlock, 3)) {
288
+ const name = heading.text.trim();
289
+ if (/^(eidos( core)?|custom( properties)?)$/i.test(name))
290
+ continue;
291
+ const match = /^(.+?)(?:\s+Properties)?$/i.exec(name);
292
+ const tool = match?.[1]?.trim() ?? '';
293
+ if (!match || tool === '' || tool.toLowerCase() === 'custom' || /^<.*>$/.test(tool))
294
+ continue;
295
+ tools[tool] = parseProperties(block, false, tool);
296
+ }
215
297
  const collections = parseCollections(body);
216
298
  for (const collection of collections) {
217
299
  if (collection.grouping) {
@@ -226,8 +308,11 @@ export function parseFrameworkMarkdown(text, root, file) {
226
308
  naming,
227
309
  namingError,
228
310
  topLevel: parseTopLevel(body),
311
+ // the markdown form predates folder types: every folder it declares is a collection
312
+ folders: collections,
229
313
  collections,
230
- schema: { core: core.length > 0 ? core : STANDARD_CORE, custom },
314
+ schema: { core: core.length > 0 ? core : STANDARD_CORE, custom, tools },
315
+ vocabulary: parseVocabulary(body),
231
316
  problems: [],
232
317
  };
233
318
  }
@@ -1,9 +1,15 @@
1
1
  // The framework model: everything a framework document declares, whichever
2
- // of `_eidos/Framework.md`, `.yaml`, or `.json` it is. The version and naming
3
- // convention, the Top-Level docs, the Collections (each with flavors, canvas
4
- // style, and grouping), and the property Schema.
2
+ // of `.eidos/Framework.md` or `.yaml` it is. The version and naming
3
+ // convention, the Top-Level docs, the Folders (every folder at the root with
4
+ // its type; a collection carries its variants and grouping), the Properties
5
+ // table (`schema` here, `properties` on disk), and the Vocabulary.
5
6
  import { kebab } from './naming.js';
6
- export const FRAMEWORK_DIR = '_eidos';
7
+ export const FRAMEWORK_DIR = '.eidos';
8
+ // This tool's name, as the standard has a tool sign what it keeps in a
9
+ // framework: its folder under `.eidos/plugins/<name>/`, and its fields on a
10
+ // Properties row (a key on the YAML entry, a column in the markdown table).
11
+ export const TOOL_KEY = 'eidosmd';
12
+ export const PLUGINS_DIR = 'plugins';
7
13
  export function isFrameworkFormat(value) {
8
14
  return value === 'md' || value === 'yaml';
9
15
  }
@@ -13,20 +19,66 @@ export const FRAMEWORK_FILES = [
13
19
  { name: 'Framework.yml', format: 'yaml' },
14
20
  ];
15
21
  export const PROPERTY_TYPES = ['Text', 'List', 'Number', 'Checkbox', 'Date', 'Date & time'];
22
+ export function readValueStyle(value) {
23
+ if (typeof value !== 'object' || value === null || Array.isArray(value))
24
+ return null;
25
+ const given = value;
26
+ const text = (field) => (typeof field === 'string' || typeof field === 'number' ? String(field).trim() : '');
27
+ const style = {};
28
+ if (text(given['shape']) !== '')
29
+ style.shape = text(given['shape']);
30
+ if (text(given['color']) !== '')
31
+ style.color = text(given['color']);
32
+ if (given['fill'] === 'solid' || given['fill'] === 'tint' || given['fill'] === 'none')
33
+ style.fill = given['fill'];
34
+ if (text(given['fill_color']) !== '')
35
+ style.fill_color = text(given['fill_color']);
36
+ if (given['size'] === 'small' || given['size'] === 'medium' || given['size'] === 'large')
37
+ style.size = given['size'];
38
+ return Object.keys(style).length > 0 ? style : null;
39
+ }
40
+ // Every folder at the root is declared with one of the standard's three
41
+ // types (Eidos 5.3.0): a collection holds blueprints; an assets folder holds
42
+ // files that are not markdown; an other folder is whatever its description
43
+ // says. The standard reads nothing inside the last two.
44
+ export const FOLDER_TYPES = ['collection', 'assets', 'other'];
45
+ export function isFolderType(value) {
46
+ return typeof value === 'string' && FOLDER_TYPES.includes(value);
47
+ }
48
+ export function isCollection(folder) {
49
+ return folder.type === 'collection';
50
+ }
51
+ // The unit a collection speaks in, the word for one of its blueprints: what
52
+ // its template files are named for (`<unit>.<variant>.md`), read off the
53
+ // default variant. A collection whose unit is `frame` is the framing one, this
54
+ // CLI's recommendation for every product; the standard names no collection.
55
+ export function unitOf(collection) {
56
+ const variant = collection.variants.find((entry) => entry.isDefault) ?? collection.variants[0];
57
+ return variant && variant.unit !== '' ? variant.unit : null;
58
+ }
16
59
  export class FrameworkError extends Error {
17
60
  constructor(message) {
18
61
  super(message);
19
62
  this.name = 'FrameworkError';
20
63
  }
21
64
  }
22
- // The standard's own five, used when a Framework.md carries no core block.
65
+ // The standard's own four (Eidos 5.3.0), two of them required, used when a
66
+ // framework document carries no core block.
23
67
  export const STANDARD_CORE = [
24
- { name: 'id', type: 'Text', appliesTo: 'all', meaning: 'Stable, unique, kebab-case identity. Assigned once, never renamed.', core: true },
25
- { name: 'title', type: 'Text', appliesTo: 'all', meaning: 'Human-readable name.', core: true },
26
- { name: 'summary', type: 'Text', appliesTo: 'all', meaning: 'One plain line: what this blueprint is. The source for the collection index.', core: true },
27
- { name: 'flavor', type: 'Text', appliesTo: 'all', meaning: 'Which flavor this blueprint follows. Absent = the collection default.', core: true },
28
- { name: 'connects_to', type: 'List', appliesTo: 'all', meaning: 'Blueprints this one connects to on the canvas, each a link.', core: true },
68
+ { name: 'id', type: 'Text', appliesTo: 'all', required: true, meaning: 'Stable, unique identity, in any form. Assigned once, never changed.', core: true, owner: 'eidos' },
69
+ { name: 'title', type: 'Text', appliesTo: 'all', required: true, meaning: 'Human-readable name.', core: true, owner: 'eidos' },
70
+ { name: 'summary', type: 'Text', appliesTo: 'all', required: false, meaning: 'One line: what this blueprint is. Feeds the index; absent, the index flags it.', core: true, owner: 'eidos' },
71
+ { name: 'variant', type: 'Text', appliesTo: 'all', required: false, meaning: "Which variant this blueprint follows. Absent = the collection's default.", core: true, owner: 'eidos' },
29
72
  ];
73
+ // Whether the standard's core requires a property of this name: what an
74
+ // absent `required` on a core row means, since the block is the standard's.
75
+ export function coreRequires(name) {
76
+ return STANDARD_CORE.some((property) => property.name === name && property.required);
77
+ }
78
+ // Every property of the Properties table, whichever block owns it, core first.
79
+ export function allProperties(schema) {
80
+ return [...schema.core, ...schema.custom, ...Object.values(schema.tools).flat()];
81
+ }
30
82
  // `Domains` -> `domain`: the custom property that carries a collection's group,
31
83
  // matched by the grouping label's singular against the properties scoped to it.
32
84
  export function groupingProperty(collection, custom) {