@krizaka/org 0.5.0 → 0.7.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 CHANGED
@@ -19,6 +19,10 @@ Without a build step, read it from the CDN: [krizaka-assets.b-cdn.net/org/v1/org
19
19
  (`defaults/v1/<kind>/<id>-<size>.<ext>`, written by krizaka-ui `tools/export-defaults.mjs`). An app never hard-codes or bundles one:
20
20
  `listDefaults(kind)` lists them, `defaultFor(kind, seed)` picks one deterministically (FNV-1a of the seed, modulo the list), `defaultUrl(item, size)` gives the URL,
21
21
  `fetchDefaults(ORG_URL)` reads the section from the published JSON. A folder is never given a person.
22
+ - **One name per entry of the family.** `org.family` (and `familyName(id)`) is the only place a name is written: the company
23
+ is « Krizaka », a product goes by its own name (Orazaka, Yomizaka, Akinaza — never « Krizaka Yomizaka »), a library or a tool
24
+ is « Krizaka <X> » (Krizaka UI, Krizaka Java, Krizaka TypeScript, Krizaka Lab; the short name only under a
25
+ heading « Open source »). The bar, the switcher and every footer read it; a test holds the rule.
22
26
  - **Public facts only.** Never an address, a phone number, figures, a secret, an IP address or an internal host: the test
23
27
  scans for them and the schema refuses any field it does not list.
24
28
  - **Validated.** `orgSchema` (JSON Schema 2020-12) and the types `Org`, `AppConfig`…; a test validates the data and requests every
@@ -29,3 +33,31 @@ Without a build step, read it from the CDN: [krizaka-assets.b-cdn.net/org/v1/org
29
33
  `MEDIA_STORAGE_API_KEY` and `MEDIA_STORAGE_ZONE`; without them it warns and publishes nothing.
30
34
 
31
35
  Apache-2.0 · [Krizaka TypeScript](https://github.com/krizaka/krizaka-ts)
36
+
37
+ ## What `org.json` is, and what it is not
38
+
39
+ **Service discovery only** (founder, 2026-11-01): the addresses and versions clients resolve (API URLs, minimum and latest app
40
+ versions, the default media, the family's names and addresses). It is read at start by the apps and published at each release.
41
+ Names, status, store availability, social links and the footer contents are **not** in it: they are admin configuration of each
42
+ app in its database, through `@krizaka/settings` (audit trail), read at runtime and at build where needed (a save redeploys).
43
+ The shared validation (no code name as a display name, FR/EN labels, valid URLs) lives in the settings package.
44
+
45
+ A rename is therefore: the administrator edits the value in the app's settings, saves, and the build-time pages redeploy.
46
+ The seed below is only the default of that setting.
47
+
48
+ ## Commercial names (`brands`, a seed)
49
+
50
+ The code names (`yomizaka`, `akinaza`, `orazaka`, `kachizaka`) stay internal: repositories, packages, coordinates, CLI. A customer
51
+ reads the commercial name, endorsed by the company — **« Verikora by Krizaka »** / **« Verikora par Krizaka »**.
52
+
53
+ ```ts
54
+ import { brandOf, displayName, lockup } from "@krizaka/org";
55
+ brandOf("akinaza").domains.shop; // "https://folioviv.shop"
56
+ displayName("yomizaka", settingValue); // the admin's choice (`brand.displayName`), else « Verikora »
57
+ lockup("orazaka", "fr"); // « Sovkora par Krizaka »
58
+ ```
59
+
60
+ **The guard.** `npx krizaka-code-names [--report] [--allow <path-part>] <built-html-dir> messages/ stores/` fails when a
61
+ public surface shows a code name (or « Krizaka ECM ») where the commercial name is required. Allowed, declared once: URLs
62
+ and hosts, `@krizaka/*` and `com.krizaka.*` coordinates, `krizaka/<repo>`, code (backticks, `<pre>`, `<code>`), class names
63
+ and attributes other than `alt` / `title` / `aria-label`, catalogue keys, and each brand's one « formerly » line.
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env node
2
+ // krizaka-code-names — a public surface never shows a code name where the commercial name is required (krizaka-internal#2036).
3
+ // krizaka-code-names [--report] [--allow <path-part>]... <file-or-dir>...
4
+ // Reads built HTML (visible text, title, description, social metadata, alt/title/aria-label), message catalogues (values only),
5
+ // and any other text file (emails, receipts, stores/*.json, llms.txt). `--allow` skips the files whose path contains the part
6
+ // (internal pages, developer pages). Exit 1 on a hit; `--report` prints and exits 0 (a repo starts as a report, then requires it).
7
+ import { readdirSync, readFileSync, statSync } from "node:fs";
8
+ import { extname, join } from "node:path";
9
+
10
+ import { catalogueValues, findCodeNames, visibleText } from "../dist/index.js";
11
+
12
+ const args = process.argv.slice(2);
13
+ const report = args.includes("--report");
14
+ const allow = [];
15
+ const roots = [];
16
+ for (let i = 0; i < args.length; i++) {
17
+ if (args[i] === "--report") continue;
18
+ if (args[i] === "--allow") allow.push(args[++i]);
19
+ else roots.push(args[i]);
20
+ }
21
+ if (roots.length === 0) {
22
+ console.error("usage: krizaka-code-names [--report] [--allow <path-part>]... <file-or-dir>...");
23
+ process.exit(2);
24
+ }
25
+ const TEXT = new Set([".html", ".htm", ".json", ".txt", ".md", ".mjml", ".hbs", ".xml", ".vtt"]);
26
+ const files = [];
27
+ const walk = (p) => {
28
+ if (allow.some((a) => p.includes(a))) return;
29
+ if (statSync(p).isDirectory()) {
30
+ for (const e of readdirSync(p)) if (e !== "node_modules" && !e.startsWith(".")) walk(join(p, e));
31
+ } else if (TEXT.has(extname(p).toLowerCase())) files.push(p);
32
+ };
33
+ roots.forEach(walk);
34
+
35
+ let count = 0;
36
+ for (const file of files) {
37
+ const raw = readFileSync(file, "utf8");
38
+ const ext = extname(file).toLowerCase();
39
+ let pieces;
40
+ if (ext === ".html" || ext === ".htm") pieces = [["", visibleText(raw)]];
41
+ else if (ext === ".json") {
42
+ try {
43
+ pieces = catalogueValues(JSON.parse(raw));
44
+ } catch {
45
+ pieces = [["", raw]];
46
+ }
47
+ } else pieces = [["", raw]];
48
+ for (const [key, text] of pieces) {
49
+ for (const hit of findCodeNames(text)) {
50
+ count++;
51
+ const where = key ? `${file} ${key}` : file;
52
+ console.log(`${where}: « ${hit.word} » → « ${hit.use} » …${hit.context}…`);
53
+ }
54
+ }
55
+ }
56
+ console.log(`${count} code name(s) on public surfaces in ${files.length} file(s)${report ? " (report only)" : ""}.`);
57
+ process.exit(count > 0 && !report ? 1 : 0);
package/dist/index.d.ts CHANGED
@@ -18,6 +18,30 @@ interface OrgSite {
18
18
  name: string;
19
19
  url: string;
20
20
  }
21
+ /** Where an entry of the family sits: the company's own pages, the products, the open-source libraries and tools, GitHub. */
22
+ type FamilyGroup = "krizaka" | "products" | "open-source" | "github";
23
+ /** `released`: open to use. `development`: built in the open, said « In development ». */
24
+ type FamilyStatus = "released" | "development";
25
+ /**
26
+ * One name of the family, the only place it is written. The naming rule (founder, 2026-10-30): the company is
27
+ * « Krizaka »; a product goes by its own name (« Yomizaka », never « Krizaka Yomizaka »); a library or a tool is always
28
+ * « Krizaka <X> » (« Krizaka UI », never « UI » alone outside a group headed « Open source »).
29
+ */
30
+ interface FamilyEntry {
31
+ id: string;
32
+ /** The display name, never translated (« Krizaka TypeScript »). */
33
+ name: string;
34
+ /** The short name, only inside its group's heading (« TypeScript » under « Open source »). */
35
+ short: string;
36
+ group: FamilyGroup;
37
+ status: FamilyStatus;
38
+ /** The day it was released (`YYYY-MM-DD`): `released` on a product or an open-source entry needs it, data not words. */
39
+ releasedAt?: string;
40
+ /** Its home: the site, or the repository while it has no site. */
41
+ url: string;
42
+ /** Its public source repository (`null` for GitHub itself). */
43
+ repo: string | null;
44
+ }
21
45
  interface OrgAvatar {
22
46
  id: string;
23
47
  /** `person` is a silhouette, `plain` a plain disc. */
@@ -112,6 +136,8 @@ interface Org {
112
136
  /** Only the products that are live. */
113
137
  products: OrgProduct[];
114
138
  sites: OrgSite[];
139
+ /** Every name of the family, in reading order (group, then order inside it): the bar, the switcher and every footer read it. */
140
+ family: FamilyEntry[];
115
141
  contact: {
116
142
  page: Localized;
117
143
  /** The public support address; `null` while none is public. */
@@ -147,6 +173,73 @@ declare const APP_SCHEMA_URL = "https://krizaka-assets.b-cdn.net/org/v1/app.sche
147
173
  /** The runtime config a mobile app may know, per live product. The app reads it at start, with a copy built into the binary as fallback. */
148
174
  declare const apps: Record<string, AppConfig>;
149
175
 
176
+ /**
177
+ * The commercial names (krizaka-internal#2033, #2035, #2036): the code name stays internal, the customer reads the
178
+ * commercial name, « Verikora by Krizaka ». Every app, site and template reads them from here — never a literal.
179
+ *
180
+ * These are NOT in `org.json`: the org JSON is service discovery only (the addresses and versions clients resolve). Names,
181
+ * status, stores and the footer are admin configuration of each app (`@krizaka/settings`, audit trail); this module is the
182
+ * seed and the validation vocabulary, not a published file.
183
+ */
184
+ /** `product`: sold on its own. `service`: a part the products carry (Kridoc, the managed storage). */
185
+ type BrandKind = "product" | "service";
186
+ interface OrgBrand {
187
+ /** The internal name: the repository, the packages, the CLI (`yomizaka`). Never on a public page. */
188
+ codeName: string;
189
+ /** The name the customer reads (`Verikora`). One capital; capitals only in the wordmark. */
190
+ commercialName: string;
191
+ kind: BrandKind;
192
+ /** The endorsement after the name: « by Krizaka » / « par Krizaka ». */
193
+ endorsement: Localized;
194
+ /** The hosts of the brand (https URLs); `null` where it has none. */
195
+ domains: {
196
+ site: string;
197
+ app: string | null;
198
+ shop: string | null;
199
+ dev: string | null;
200
+ };
201
+ /** The one dated « formerly » line of the transition; `null` when the code name was never public. */
202
+ formerly: Localized | null;
203
+ }
204
+ /** The seed: one commercial name per code name. Nothing flips to it until the readiness checklist (krizaka-internal#2092). */
205
+ declare const brands: readonly OrgBrand[];
206
+ /** The brand of a code name (`brandOf("yomizaka").commercialName` → « Verikora »). */
207
+ declare const brandOf: (codeName: string) => OrgBrand;
208
+ /** The longest name an administrator may set (a bar, a store listing and an email subject must hold it). */
209
+ declare const DISPLAY_NAME_MAX = 30;
210
+ /**
211
+ * The name an app shows: the administrator's choice (`brand.displayName` in its settings) when one is set, else the
212
+ * commercial name. Never the code name: a setting equal to a code name falls back to the commercial name.
213
+ */
214
+ declare const displayName: (codeName: string, chosen?: string | null) => string;
215
+ /** The lockup in words: « Verikora by Krizaka » / « Verikora par Krizaka ». `fr` reads French, anything else English. */
216
+ declare const lockup: (codeName: string, locale: string, chosen?: string | null) => string;
217
+ /** The words a public surface never shows: every code name, and « Krizaka ECM » (it is Kridoc). */
218
+ declare const CODE_NAMES: readonly string[];
219
+ /** The code name → commercial name map the guard prints in its hints. */
220
+ declare const codeNameMap: () => Record<string, string>;
221
+ interface CodeNameHit {
222
+ /** The word found, as written. */
223
+ word: string;
224
+ /** What to write instead. */
225
+ use: string;
226
+ /** Up to 40 characters around it. */
227
+ context: string;
228
+ }
229
+ /** The code names a piece of customer-facing text shows (case-insensitive, whole words), minus the allowed spans. */
230
+ declare const findCodeNames: (text: string) => CodeNameHit[];
231
+ /**
232
+ * The text a visitor reads in a built HTML page: the body text, the title, the description and social metadata, and the
233
+ * `alt`, `title` and `aria-label` attributes. Scripts, styles, `<pre>` and `<code>` (developer pages' commands) and
234
+ * comments are left out, and so are class names and links (a `brand-yomizaka` class or a GitHub href is not shown).
235
+ */
236
+ declare const visibleText: (html: string) => string;
237
+ /**
238
+ * The values of a message catalogue (EN/FR JSON), keyed by their path. A key path may name a code name
239
+ * (`site.yomizaka.title`): keys are structure; only the values are read by a customer.
240
+ */
241
+ declare const catalogueValues: (catalogue: unknown, path?: string) => [string, string][];
242
+
150
243
  /** The products that have a demo cast. */
151
244
  type CastProduct = "orazaka" | "yomizaka" | "akinaza";
152
245
  /** What a persona does in the app: a member, a shop owner or a customer of one, an administrator. */
@@ -207,7 +300,6 @@ declare function fetchDefaults(url: string, fetchImpl?: typeof fetch): Promise<O
207
300
  declare const CDN = "https://krizaka-assets.b-cdn.net";
208
301
  declare const ORG_URL = "https://krizaka-assets.b-cdn.net/org/v1/org.json";
209
302
  declare const ORG_SCHEMA_URL = "https://krizaka-assets.b-cdn.net/org/v1/org.schema.json";
210
- /** The public facts about Krizaka. Reviewed by pull request; changing a fact is one PR here and a release. */
211
303
  declare const org: Org;
212
304
 
213
305
  /** The JSON Schema of `org/v1/org.json`. Nothing outside it is allowed (`additionalProperties: false` everywhere): a new fact is a new line here first. */
@@ -217,7 +309,7 @@ declare const orgSchema: {
217
309
  readonly title: "Krizaka organisation — public facts";
218
310
  readonly type: "object";
219
311
  readonly additionalProperties: false;
220
- readonly required: readonly ["$schema", "schemaVersion", "version", "updatedAt", "company", "brand", "people", "defaults", "products", "sites", "contact", "footer"];
312
+ readonly required: readonly ["$schema", "schemaVersion", "version", "updatedAt", "company", "brand", "people", "defaults", "products", "sites", "family", "contact", "footer"];
221
313
  readonly properties: {
222
314
  readonly $schema: {
223
315
  readonly type: "string";
@@ -695,6 +787,53 @@ declare const orgSchema: {
695
787
  };
696
788
  };
697
789
  };
790
+ readonly family: {
791
+ readonly type: "array";
792
+ readonly minItems: 1;
793
+ readonly items: {
794
+ readonly type: "object";
795
+ readonly additionalProperties: false;
796
+ readonly required: readonly ["id", "name", "short", "group", "status", "url", "repo"];
797
+ readonly properties: {
798
+ readonly id: {
799
+ readonly type: "string";
800
+ readonly pattern: "^[a-z0-9]+(-[a-z0-9]+)*$";
801
+ };
802
+ readonly name: {
803
+ readonly type: "string";
804
+ readonly minLength: 1;
805
+ };
806
+ readonly short: {
807
+ readonly type: "string";
808
+ readonly minLength: 1;
809
+ };
810
+ readonly group: {
811
+ readonly enum: readonly ["krizaka", "products", "open-source", "github"];
812
+ };
813
+ readonly status: {
814
+ readonly enum: readonly ["released", "development"];
815
+ };
816
+ readonly releasedAt: {
817
+ readonly type: "string";
818
+ readonly pattern: "^\\d{4}-\\d{2}-\\d{2}$";
819
+ };
820
+ readonly url: {
821
+ readonly type: "string";
822
+ readonly format: "uri";
823
+ readonly pattern: "^https://";
824
+ };
825
+ readonly repo: {
826
+ readonly anyOf: readonly [{
827
+ readonly type: "string";
828
+ readonly format: "uri";
829
+ readonly pattern: "^https://";
830
+ }, {
831
+ readonly type: "null";
832
+ }];
833
+ };
834
+ };
835
+ };
836
+ };
698
837
  readonly contact: {
699
838
  readonly type: "object";
700
839
  readonly additionalProperties: false;
@@ -858,5 +997,8 @@ declare const appSchema: {
858
997
  declare const localized: (text: Localized, locale: string) => string;
859
998
  /** The footer line of the family, « Une entreprise de Technologies Krizaka · Fait à Montréal ». */
860
999
  declare const footerLine: (locale: string) => string;
1000
+ /** A name of the family by id (`familyName("ts")` → « Krizaka TypeScript »): the only way a site writes one. */
1001
+ declare const familyEntry: (id: string) => FamilyEntry;
1002
+ declare const familyName: (id: string) => string;
861
1003
 
862
- export { APP_SCHEMA_URL, type AppConfig, type AppEnvironment, CAST, CAST_VERSION, CDN, type CastPersona, type CastProduct, type CastRole, DEFAULT_KINDS, type Localized, ORG_SCHEMA_URL, ORG_URL, type Org, type OrgAvatar, type OrgBanner, type OrgDefaultItem, type OrgDefaultKind, type OrgDefaults, type OrgMark, type OrgProduct, type OrgSite, type ProductStatus, appSchema, apps, castMedia, castOf, defaultFor, defaultUrl, defaults, fetchDefaults, footerLine, listDefaults, localized, org, orgSchema, persona, personaByRole, seedFingerprint, seedHash };
1004
+ export { APP_SCHEMA_URL, type AppConfig, type AppEnvironment, type BrandKind, CAST, CAST_VERSION, CDN, CODE_NAMES, type CastPersona, type CastProduct, type CastRole, type CodeNameHit, DEFAULT_KINDS, DISPLAY_NAME_MAX, type FamilyEntry, type FamilyGroup, type FamilyStatus, type Localized, ORG_SCHEMA_URL, ORG_URL, type Org, type OrgAvatar, type OrgBanner, type OrgBrand, type OrgDefaultItem, type OrgDefaultKind, type OrgDefaults, type OrgMark, type OrgProduct, type OrgSite, type ProductStatus, appSchema, apps, brandOf, brands, castMedia, castOf, catalogueValues, codeNameMap, defaultFor, defaultUrl, defaults, displayName, familyEntry, familyName, fetchDefaults, findCodeNames, footerLine, listDefaults, localized, lockup, org, orgSchema, persona, personaByRole, seedFingerprint, seedHash, visibleText };