tadforce 0.2.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.
- package/.claude-plugin/marketplace.json +3 -3
- package/.claude-plugin/plugin.json +2 -2
- package/README.md +15 -5
- package/package.json +2 -2
- package/skills/tadforce/SKILL.md +1 -1
- package/skills/tadforce/references/questions.md +1 -1
- package/skills/tadforce/references/workflow.md +10 -0
- package/src/build/render.js +1 -1
- package/src/cli.js +1 -1
- package/src/commands/docs.js +14 -1
- package/src/commands/doctor.js +8 -4
- package/src/commands/init.js +3 -3
- package/src/commands/update.js +2 -1
- package/src/util/i18n.js +24 -8
- package/README.fr.md +0 -125
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
"name": "Jihad AZAMI HASSANI"
|
|
5
5
|
},
|
|
6
6
|
"metadata": {
|
|
7
|
-
"description": "TADForce —
|
|
7
|
+
"description": "TADForce — AI-Powered Salesforce Technical Documentation, written by your agent from the org's own metadata."
|
|
8
8
|
},
|
|
9
9
|
"plugins": [
|
|
10
10
|
{
|
|
11
11
|
"name": "tadforce",
|
|
12
12
|
"source": "./",
|
|
13
|
-
"description": "
|
|
14
|
-
"version": "0.
|
|
13
|
+
"description": "AI-Powered Salesforce Technical Documentation: generate, review, validate and update the Technical Architecture Document of any Salesforce org (/tadforce, /tadforce-review, /tadforce-update).",
|
|
14
|
+
"version": "0.3.0",
|
|
15
15
|
"author": { "name": "Jihad AZAMI HASSANI" },
|
|
16
16
|
"license": "Apache-2.0",
|
|
17
17
|
"category": "documentation"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tadforce",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "AI-Powered Salesforce Technical Documentation — the Technical Architecture Document of any Salesforce org, written from the org's own metadata: evidence-based, reproducible, reviewable in the browser, kept up to date. All Salesforce products and industries.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Jihad AZAMI HASSANI",
|
|
7
7
|
"url": "https://github.com/azamiji"
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
<img src="assets/banner.svg" alt="TADForce — point it at a Salesforce org, get the architecture document a senior architect would write" width="100%">
|
|
4
4
|
|
|
5
|
-
###
|
|
5
|
+
### AI-Powered Salesforce Technical Documentation
|
|
6
6
|
|
|
7
7
|
**Four commands. One org. One Technical Architecture Document — evidence-based, reproducible, reviewable in the browser.**
|
|
8
8
|
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
[](#-quick-start)
|
|
21
21
|
[](#-read-only-by-design)
|
|
22
22
|
|
|
23
|
-
[**Live demo**](https://azamiji.github.io/TADForce/) · [Quick start](#-quick-start) · [The four commands](#-four-commands) · [Every project type](#-every-kind-of-salesforce-project)
|
|
23
|
+
[**Live demo**](https://azamiji.github.io/TADForce/) · [Quick start](#-quick-start) · [The four commands](#-four-commands) · [Every project type](#-every-kind-of-salesforce-project)
|
|
24
24
|
|
|
25
25
|
</div>
|
|
26
26
|
|
|
@@ -140,7 +140,7 @@ The document carries **the client's logo and colours**: the logo is searched in
|
|
|
140
140
|
| **No org access**, sources only | `--source local` documents the package directories of the project |
|
|
141
141
|
| **Unlocked or managed packages** | Namespaces and installed packages are detected and documented with their evidence |
|
|
142
142
|
| **Any cloud, any industry** | Sections switch on only when the product is detected, each with its writing guide (below) |
|
|
143
|
-
| **
|
|
143
|
+
| **Any language** | English and French built in. Any other language (Spanish, German, Italian, Portuguese, Arabic, Japanese…): pick it in `doctor`, your agent translates the headings and labels once, then writes in that language. Salesforce terms stay in English |
|
|
144
144
|
|
|
145
145
|
### Products and industries
|
|
146
146
|
|
|
@@ -219,7 +219,7 @@ The TADForce CLI sends nothing anywhere: no telemetry, no server, no account. It
|
|
|
219
219
|
|
|
220
220
|
## 🗺️ Roadmap
|
|
221
221
|
|
|
222
|
-
- [ ]
|
|
222
|
+
- [ ] Built-in skeletons for more languages (es, de, pt, it) — any language already works through the agent; contributions welcome
|
|
223
223
|
- [ ] Your own template: add, hide or rename chapters from `tadforce/templates/`
|
|
224
224
|
- [ ] More examples: Service + Experience Cloud, an ISV package
|
|
225
225
|
- [ ] Salesforce CLI plugin wrapper (`sf tad …`)
|
|
@@ -232,7 +232,17 @@ Issues and pull requests are welcome — new product modules, new extractors, ne
|
|
|
232
232
|
|
|
233
233
|
## 👥 Community
|
|
234
234
|
|
|
235
|
-
TADForce was born
|
|
235
|
+
TADForce was born for the **Salesforce ecosystem**: architects, consultants, integrators and in-house teams who need documentation they can trust.
|
|
236
|
+
|
|
237
|
+
Your experience is what makes it better. Tell us, in the [issues](https://github.com/azamiji/TADForce/issues) and [discussions](https://github.com/azamiji/TADForce/discussions):
|
|
238
|
+
|
|
239
|
+
- **How it went** — the kind of org and project, the time it took, what worked out of the box.
|
|
240
|
+
- **How good the result was** — sections that read like a senior architect wrote them, and sections that did not.
|
|
241
|
+
- **What to improve** — a product it should detect better, a section that misses the point, a question it should have asked.
|
|
242
|
+
|
|
243
|
+
Please share your feedback, not your documents: they describe your clients' systems.
|
|
244
|
+
|
|
245
|
+
If TADForce saved you a week, **star the repo** ⭐ — it is how other Salesforce teams find it.
|
|
236
246
|
|
|
237
247
|
## 👤 Author
|
|
238
248
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tadforce",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "AI-Powered Salesforce Technical Documentation — the Technical Architecture Document of any Salesforce org, written from the org's own metadata: evidence-based, reproducible, reviewable in the browser, kept up to date.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"tadforce": "bin/tadforce.js"
|
package/skills/tadforce/SKILL.md
CHANGED
|
@@ -19,7 +19,7 @@ The CLI (`npx tadforce …`) does everything deterministic: org access, retrieve
|
|
|
19
19
|
6. **Numbers come from facts.** Write counts as `{{n:apex.classes}}`, links as `{{ref:section-id}}`. Never type a count by hand.
|
|
20
20
|
7. **Client document ≠ internal review.** The TAD is factual and positive. Debt, risks and recommendations go to the Improvement Axes document.
|
|
21
21
|
8. **Technical names in code font must exist** in the inventory (`tadforce validate` checks it).
|
|
22
|
-
9. **Write in the document language** (`tadforce.config.json › language`). Keep Salesforce terms in English (Lead, Queue, Record Type, Named Credential…).
|
|
22
|
+
9. **Write in the document language** (`tadforce.config.json › language` — any language; outside English and French, translate the headings and labels once, see `references/workflow.md › Other languages`). Keep Salesforce terms in English (Lead, Queue, Record Type, Named Credential…).
|
|
23
23
|
10. **Ask in batches.** Group your questions; never ask what the facts already answer.
|
|
24
24
|
|
|
25
25
|
## The run
|
|
@@ -6,7 +6,7 @@ Ask in **blocks**, each in one message, with your proposed answer for every item
|
|
|
6
6
|
|
|
7
7
|
`npx tadforce doctor --json` returns these questions with a `recommended` answer each (from the org when connected). Ask them all in one message:
|
|
8
8
|
|
|
9
|
-
1. Document language — recommended: the org default language.
|
|
9
|
+
1. Document language — recommended: the org default language. English and French are built in; any other language works (the headings and labels are translated once, see `workflow.md › Other languages`).
|
|
10
10
|
2. Client name and short code (file names) — recommended: `Organization.Name` / `<CODE>`.
|
|
11
11
|
3. Integrator — **optional**. Blank: the document names the client only (no empty « Integrator » line, no badge).
|
|
12
12
|
4. Author — **optional**. Blank: no author is shown anywhere. Never put your own name or the agent's.
|
|
@@ -25,6 +25,16 @@ What `doctor` does by itself, read-only:
|
|
|
25
25
|
- metadata, as chosen (`source`): **org** (recommended — the org is the truth) retrieves every useful type with a `*` manifest into `tadforce/.snapshot` (`force-app` is never touched; large orgs take several minutes: tell the human); **local** reads the package directories; **both** combines them. Then `facts/facts.json`, `facts/org.json` (aggregates), `facts/questions.json`, the chapter skeletons in `tadforce/src/`;
|
|
26
26
|
- branding from evidence: the client logo searched in the snapshot (static resources, content assets, site assets named *logo*), the colours from the org's Lightning theme (`BrandingSet`), else from the logo. Never fetch a logo from the web without the human's go-ahead; when they agree, pass its URL with `--logo`.
|
|
27
27
|
|
|
28
|
+
### Other languages
|
|
29
|
+
|
|
30
|
+
English and French are built in. Any other language (`--lang es`, `de`, `it`, `pt`, `nl`, `ar`, `ja`…) works too: the skeletons are scaffolded in English and `tadforce/i18n/<lang>.json` is created from the English labels. Once, before writing:
|
|
31
|
+
|
|
32
|
+
1. Translate the headings of every `tadforce/src/*/*.md` file into the document language. Keep the `{#anchors}`, the `{-}` markers, the `<!-- tad:… -->` lines and the `@@…@@` directives exactly as they are.
|
|
33
|
+
2. Translate every value of `tadforce/i18n/<lang>.json` (cover, tables, figures, buttons of the HTML). Keep the keys and the `{placeholders}`; set `meta.htmlLang`. Delete the `_translate` line.
|
|
34
|
+
3. Write the sections in that language; Salesforce terms stay in English.
|
|
35
|
+
|
|
36
|
+
`doctor` shows a reminder until step 2 is done. To ship the language for everyone, contribute it (issue template « New language »).
|
|
37
|
+
|
|
28
38
|
### Optional inputs
|
|
29
39
|
|
|
30
40
|
- **Functional specifications** (`--specs`): copied into `knowledge/inputs/specs/`. Read them before chapters 3 and 5; they are evidence level P2/P3 (`evidence-rules.md`). They explain the *why*; the metadata still decides the *what*.
|
package/src/build/render.js
CHANGED
|
@@ -107,7 +107,7 @@ export async function buildDocument(wsCtx, doc, opts = {}) {
|
|
|
107
107
|
const problems = [];
|
|
108
108
|
const facts = loadFacts(wsCtx.ws);
|
|
109
109
|
if (!facts) throw new Error('facts/facts.json not found: run `tadforce facts` first.');
|
|
110
|
-
const { lang, dict } = loadI18n(cfg.language);
|
|
110
|
+
const { lang, dict } = loadI18n(cfg.language, wsCtx.ws);
|
|
111
111
|
const glossary = wsCtx.glossary() || {};
|
|
112
112
|
const srcDir = wsCtx.p('src', doc);
|
|
113
113
|
if (!exists(srcDir)) throw new Error(`No sources in ${srcDir}: run \`tadforce scaffold --doc ${doc}\`.`);
|
package/src/cli.js
CHANGED
|
@@ -38,7 +38,7 @@ const COMMANDS = { ...MAIN, ...ADVANCED };
|
|
|
38
38
|
|
|
39
39
|
function help(all) {
|
|
40
40
|
const v = readJson(path.join(PKG_ROOT, 'package.json')).version;
|
|
41
|
-
console.log(`${c.violet('▲')} ${c.bold('TADForce')} ${v} —
|
|
41
|
+
console.log(`${c.violet('▲')} ${c.bold('TADForce')} ${v} — AI-Powered Salesforce Technical Documentation. ${c.dim('No evidence, no sentence.')}\n`);
|
|
42
42
|
console.log(`Usage: ${c.bold('npx tadforce <command> [options]')}\n`);
|
|
43
43
|
for (const [k, [, d]] of Object.entries(MAIN)) console.log(` ${c.cyan(k.padEnd(15))} ${d}`);
|
|
44
44
|
if (all) {
|
package/src/commands/docs.js
CHANGED
|
@@ -4,6 +4,7 @@ import path from 'node:path';
|
|
|
4
4
|
import { exists, writeJson, readText, copyDir, stableStringify } from '../util/fsx.js';
|
|
5
5
|
import { loadWorkspace, sourceDirs } from '../util/config.js';
|
|
6
6
|
import { TEMPLATES } from '../util/paths.js';
|
|
7
|
+
import { LANGUAGES, normalizeLang, templateLang } from '../util/i18n.js';
|
|
7
8
|
import { extractFacts, readCodeAnalyzer, loadFacts } from '../facts/index.js';
|
|
8
9
|
import { collectOrgFacts } from '../facts/org.js';
|
|
9
10
|
import { fingerprints } from '../facts/fingerprints.js';
|
|
@@ -70,7 +71,19 @@ export function buildQuestions(f, orgFacts) {
|
|
|
70
71
|
|
|
71
72
|
export async function scaffold(args) {
|
|
72
73
|
const ctx = loadWorkspace(args.cwd || process.cwd());
|
|
73
|
-
const lang = ctx.cfg.language
|
|
74
|
+
const lang = templateLang(ctx.cfg.language);
|
|
75
|
+
const target = normalizeLang(ctx.cfg.language);
|
|
76
|
+
// Any other language: English skeletons and labels, translated once by the agent in the workspace.
|
|
77
|
+
if (!LANGUAGES.includes(target)) {
|
|
78
|
+
const own = ctx.p('i18n', `${target}.json`);
|
|
79
|
+
if (!exists(own)) {
|
|
80
|
+
const en = JSON.parse(readText(path.join(TEMPLATES, 'i18n', 'en.json')));
|
|
81
|
+
en.meta.htmlLang = target;
|
|
82
|
+
en._translate = `Translate every value of this file into the document language (${target}); keep the keys and the {placeholders}. Then delete this line.`;
|
|
83
|
+
writeJson(own, en);
|
|
84
|
+
}
|
|
85
|
+
log.human(`document language "${target}": the skeletons are in English. Your agent translates the headings of src/*/*.md (keep the {#anchors}) and the labels of tadforce/i18n/${target}.json, once.`);
|
|
86
|
+
}
|
|
74
87
|
for (const doc of docsOf(args)) {
|
|
75
88
|
const src = path.join(TEMPLATES, doc, lang);
|
|
76
89
|
const dst = ctx.p('src', doc);
|
package/src/commands/doctor.js
CHANGED
|
@@ -8,7 +8,7 @@ import YAML from 'yaml';
|
|
|
8
8
|
import { exists, readJson, readText, writeText } from '../util/fsx.js';
|
|
9
9
|
import { findWorkspace, defaultConfig, DEFAULT_BRAND, loadWorkspace, sourceDirs } from '../util/config.js';
|
|
10
10
|
import { CONFIG_FILE } from '../util/paths.js';
|
|
11
|
-
import { LANGUAGES } from '../util/i18n.js';
|
|
11
|
+
import { LANGUAGES, normalizeLang } from '../util/i18n.js';
|
|
12
12
|
import { sfVersion, sfJson, sfInteractive, soql } from '../sf/sf.js';
|
|
13
13
|
import { findBrowser, closeBrowser } from '../build/browser.js';
|
|
14
14
|
import { findLogoCandidates, orgThemeColors, imageColors, paletteFrom } from '../build/brand.js';
|
|
@@ -65,7 +65,7 @@ async function proposals(alias) {
|
|
|
65
65
|
const out = {};
|
|
66
66
|
try {
|
|
67
67
|
const o = ((await soql('SELECT Name, IsSandbox, LanguageLocaleKey FROM Organization', alias)).records || [])[0] || {};
|
|
68
|
-
out.client = o.Name; out.language =
|
|
68
|
+
out.client = o.Name; out.language = normalizeLang(String(o.LanguageLocaleKey || 'en').split('_')[0]); out.environment = o.IsSandbox ? 'Sandbox' : 'Production';
|
|
69
69
|
} catch { /* proposals stay empty */ }
|
|
70
70
|
return out;
|
|
71
71
|
}
|
|
@@ -76,7 +76,7 @@ const listArg = (v) => (typeof v !== 'string' ? undefined : v.split(',').map((x)
|
|
|
76
76
|
async function applyAnswers(ctx, args) {
|
|
77
77
|
const cfg = ctx.cfg;
|
|
78
78
|
const set = (cond, fn) => { if (cond) fn(); };
|
|
79
|
-
set(args.lang, () => { cfg.language =
|
|
79
|
+
set(args.lang, () => { cfg.language = normalizeLang(args.lang); });
|
|
80
80
|
set(args.client, () => { cfg.client.name = String(args.client); if (!args.short && (cfg.client.short === 'CLIENT' || !cfg.client.short)) cfg.client.short = String(args.client).replace(/[^A-Za-z0-9]/g, '').slice(0, 8).toUpperCase(); });
|
|
81
81
|
set(args.short, () => { cfg.client.short = String(args.short); });
|
|
82
82
|
set(args.integrator !== undefined, () => { cfg.integrator.name = args.integrator === true || args.integrator === '' || args.integrator === 'none' ? null : String(args.integrator); });
|
|
@@ -164,7 +164,7 @@ function framingQuestions(cfg, prop, alias, hasLocal) {
|
|
|
164
164
|
const q = [];
|
|
165
165
|
const ask = (id, question, recommended, extra = {}) => q.push({ id, question, recommended, ...extra });
|
|
166
166
|
if (!cfg.setup.confirmed) {
|
|
167
|
-
ask('lang',
|
|
167
|
+
ask('lang', `Document language — ${LANGUAGES.join(', ')} built in, or any language code (es, de, it, pt, nl, ar, ja…): the agent then translates the headings and labels once`, prop.language || cfg.language, { options: [...LANGUAGES, 'other'] });
|
|
168
168
|
ask('client', 'Client name, as it must appear in the document', prop.client || cfg.client.name);
|
|
169
169
|
ask('short', 'Client short name (file names, references)', cfg.client.short !== 'CLIENT' ? cfg.client.short : String(prop.client || cfg.client.name).replace(/[^A-Za-z0-9]/g, '').slice(0, 8).toUpperCase());
|
|
170
170
|
ask('integrator', 'Integrator, if any (blank: the document only names the client)', cfg.integrator.name || '', { optional: true });
|
|
@@ -240,6 +240,9 @@ async function evaluate(project, args) {
|
|
|
240
240
|
const ctx = loadWorkspace(project);
|
|
241
241
|
snapshot = exists(ctx.p(ctx.cfg.source.dir));
|
|
242
242
|
facts = exists(ctx.p('facts', 'facts.json'));
|
|
243
|
+
const lang = normalizeLang(ctx.cfg.language);
|
|
244
|
+
const own = ctx.p('i18n', `${lang}.json`);
|
|
245
|
+
if (!LANGUAGES.includes(lang) && exists(own) && readJson(own)._translate) add('language', true, `Document language "${lang}": headings and labels still to translate (once)`, `the agent translates the headings of src/*/*.md (keep the {#anchors}) and the values of tadforce/i18n/${lang}.json, then removes its "_translate" line`, false, 'warn');
|
|
243
246
|
add('metadata', facts, facts ? `Metadata: ${snapshot ? 'retrieved from the org' : 'local sources'} → facts/facts.json` : 'Metadata not collected yet', facts ? null : 'doctor retrieves it once the questions are answered');
|
|
244
247
|
}
|
|
245
248
|
return { tools, ws, cfg, alias, status, list, facts, snapshot };
|
|
@@ -298,6 +301,7 @@ export async function run(args) {
|
|
|
298
301
|
for (const q of framing) {
|
|
299
302
|
const a = (await rl.question(` ${q.question}${q.options ? ` (${q.options.join('/')})` : ''}${q.recommended ? ` [${q.recommended}]` : ''}: `)).trim();
|
|
300
303
|
answers[q.id] = a || q.recommended || (q.optional ? '' : undefined);
|
|
304
|
+
if (q.id === 'lang' && /^(other|autre)$/i.test(a)) answers.lang = (await rl.question(' Language code (es, de, it, pt, nl, ar, ja…): ')).trim() || 'en';
|
|
301
305
|
}
|
|
302
306
|
const flags = { confirm: true, specs: answers.specs || false };
|
|
303
307
|
for (const [k, v] of Object.entries(answers)) if (v !== undefined) flags[k === 'logo' ? 'logo' : k] = v === '' && ['integrator', 'author'].includes(k) ? 'none' : v === '' ? undefined : v;
|
package/src/commands/init.js
CHANGED
|
@@ -4,7 +4,7 @@ import readline from 'node:readline/promises';
|
|
|
4
4
|
import { exists, writeText, writeJson, copyDir, readJson } from '../util/fsx.js';
|
|
5
5
|
import { defaultConfig, findWorkspace } from '../util/config.js';
|
|
6
6
|
import { WORKSPACE_DIR, CONFIG_FILE, SKILLS } from '../util/paths.js';
|
|
7
|
-
import { LANGUAGES } from '../util/i18n.js';
|
|
7
|
+
import { LANGUAGES, normalizeLang } from '../util/i18n.js';
|
|
8
8
|
import { log } from '../util/log.js';
|
|
9
9
|
import { run as doctor } from './doctor.js';
|
|
10
10
|
|
|
@@ -100,7 +100,7 @@ export async function run(args) {
|
|
|
100
100
|
const prev = existing ? readJson(path.join(ws, CONFIG_FILE)) : {};
|
|
101
101
|
const base = defaultConfig(prev);
|
|
102
102
|
if (interactive) log.step('A few facts only you can confirm (Enter keeps the default):');
|
|
103
|
-
const language = args.lang || (await ask(rl, `Document language (${LANGUAGES.join('/')})`, base.language));
|
|
103
|
+
const language = args.lang || (await ask(rl, `Document language (${LANGUAGES.join('/')} built in, or any code: es, de, it, pt, ar…)`, base.language));
|
|
104
104
|
const clientName = args.client || (await ask(rl, 'Client name', prev.client?.name || base.client.name));
|
|
105
105
|
const short = args.short || (await ask(rl, 'Client short name (used in file names)', prev.client?.short || clientName.replace(/[^A-Za-z0-9]/g, '').slice(0, 8).toUpperCase()));
|
|
106
106
|
const integrator = args.integrator || (await ask(rl, 'Integrator (blank if none)', base.integrator.name || ''));
|
|
@@ -109,7 +109,7 @@ export async function run(args) {
|
|
|
109
109
|
const alias = args.org || (await ask(rl, 'Salesforce org alias (blank: decide later)', base.org.alias || ''));
|
|
110
110
|
const cfg = defaultConfig({
|
|
111
111
|
...prev,
|
|
112
|
-
language:
|
|
112
|
+
language: normalizeLang(language),
|
|
113
113
|
client: { ...base.client, name: clientName, short },
|
|
114
114
|
integrator: { ...base.integrator, name: integrator || null },
|
|
115
115
|
document: { ...base.document, author: author || null, title, reference: prev.document?.reference || `TAD-${short}-CRM`, environment: args.environment || base.document.environment, status: language === 'fr' && !prev.document?.status ? 'Pour revue' : base.document.status, classification: language === 'fr' && !prev.document?.classification ? 'Confidentiel' : base.document.classification },
|
package/src/commands/update.js
CHANGED
|
@@ -7,6 +7,7 @@ import { AREAS, fingerprints } from '../facts/fingerprints.js';
|
|
|
7
7
|
import { loadWorkspace, sourceDirs } from '../util/config.js';
|
|
8
8
|
import { TEMPLATES } from '../util/paths.js';
|
|
9
9
|
import { log, c } from '../util/log.js';
|
|
10
|
+
import { templateLang } from '../util/i18n.js';
|
|
10
11
|
import * as org from './org.js';
|
|
11
12
|
import * as docs from './docs.js';
|
|
12
13
|
|
|
@@ -121,7 +122,7 @@ export async function run(args) {
|
|
|
121
122
|
for (const m of modulesAfter.filter((x) => !modulesBefore.includes(x))) note('modules', `new product detected: ${m}`);
|
|
122
123
|
for (const m of modulesBefore.filter((x) => !modulesAfter.includes(x))) note('modules', `product no longer detected: ${m}`);
|
|
123
124
|
const specs = readYaml(path.join(TEMPLATES, 'specs', 'sections.yml'), {}).sections || {};
|
|
124
|
-
const sections = affectedSections(areas, specs, sectionLocations(ctx2.cfg.language
|
|
125
|
+
const sections = affectedSections(areas, specs, sectionLocations(templateLang(ctx2.cfg.language)));
|
|
125
126
|
|
|
126
127
|
// 4. New version in the document control.
|
|
127
128
|
const cfg = ctx2.cfg;
|
package/src/util/i18n.js
CHANGED
|
@@ -2,15 +2,30 @@ import path from 'node:path';
|
|
|
2
2
|
import { readJson, exists } from './fsx.js';
|
|
3
3
|
import { TEMPLATES } from './paths.js';
|
|
4
4
|
|
|
5
|
+
/** Languages shipped with skeletons and interface labels. Any other language works from the English ones. */
|
|
5
6
|
export const LANGUAGES = ['en', 'fr'];
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
/** A language code as written in the config: en, fr, es, de, pt-BR… (anything else falls back to en). */
|
|
9
|
+
export function normalizeLang(lang) {
|
|
10
|
+
const m = /^([a-z]{2,3})(?:[-_]([a-z]{2}))?$/i.exec(String(lang || '').trim());
|
|
11
|
+
return m ? m[1].toLowerCase() + (m[2] ? '-' + m[2].toUpperCase() : '') : 'en';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Skeletons and specs come in the built-in languages; other languages start from English. */
|
|
15
|
+
export const templateLang = (lang) => (LANGUAGES.includes(normalizeLang(lang)) ? normalizeLang(lang) : 'en');
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Interface and document labels: English, then the built-in translation, then the workspace's own
|
|
19
|
+
* file (tadforce/i18n/<lang>.json) — the translation the agent writes for any other language.
|
|
20
|
+
*/
|
|
21
|
+
export function loadI18n(lang, ws = null) {
|
|
22
|
+
const l = normalizeLang(lang);
|
|
23
|
+
let dict = readJson(path.join(TEMPLATES, 'i18n', 'en.json'));
|
|
24
|
+
const builtIn = path.join(TEMPLATES, 'i18n', `${l}.json`);
|
|
25
|
+
if (l !== 'en' && exists(builtIn)) dict = merge(dict, readJson(builtIn));
|
|
26
|
+
const own = ws && path.join(ws, 'i18n', `${l}.json`);
|
|
27
|
+
if (own && exists(own)) dict = merge(dict, readJson(own));
|
|
28
|
+
return { lang: l, dict };
|
|
14
29
|
}
|
|
15
30
|
|
|
16
31
|
function merge(a, b) {
|
|
@@ -29,5 +44,6 @@ export function t(dict, key, vars = {}) {
|
|
|
29
44
|
export function formatNumber(n, lang) {
|
|
30
45
|
if (typeof n !== 'number' || !Number.isFinite(n)) return String(n);
|
|
31
46
|
// fr-FR uses a narrow no-break space as thousands separator, as in the reference document.
|
|
32
|
-
|
|
47
|
+
const locale = lang === 'fr' ? 'fr-FR' : lang === 'en' || !lang ? 'en-US' : lang;
|
|
48
|
+
try { return new Intl.NumberFormat(locale).format(n); } catch { return new Intl.NumberFormat('en-US').format(n); }
|
|
33
49
|
}
|
package/README.fr.md
DELETED
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
<div align="center">
|
|
2
|
-
|
|
3
|
-
<img src="assets/banner.svg" alt="TADForce" width="100%">
|
|
4
|
-
|
|
5
|
-
### Le scribe de l'architecte pour les orgs Salesforce.
|
|
6
|
-
|
|
7
|
-
**Quatre commandes. Une org. Un document d'architecture technique fondé sur des preuves, reproductible, relu dans le navigateur.**
|
|
8
|
-
|
|
9
|
-
[English](README.md) · [**Démo en ligne**](https://azamiji.github.io/TADForce/)
|
|
10
|
-
|
|
11
|
-
</div>
|
|
12
|
-
|
|
13
|
-
<img src="assets/divider.svg" width="100%" alt="">
|
|
14
|
-
|
|
15
|
-
## Pourquoi TADForce
|
|
16
|
-
|
|
17
|
-
Rédiger le document d'architecture technique d'une org Salesforce prend des semaines, et il est périmé à la release suivante. Un chat IA générique l'écrit en une heure, puis on passe des jours à vérifier ce qu'il a inventé.
|
|
18
|
-
|
|
19
|
-
TADForce lit l'org comme le ferait un architecte Salesforce senior — chaque classe, Flow, objet, Permission Set Group, Named Credential et OmniScript — et votre agent IA rédige le document à partir de ces preuves. Une seule règle : **pas de preuve, pas de phrase.** Si les métadonnées ne le prouvent pas, l'agent vous pose la question ou écrit « à confirmer ». Il ne devine jamais le nom d'un système, ne documente jamais une fonction décommissionnée et ne tape jamais un chiffre à la main.
|
|
20
|
-
|
|
21
|
-
<div align="center">
|
|
22
|
-
<img src="assets/gifs/document.gif" alt="Navigation dans un document généré par TADForce" width="92%">
|
|
23
|
-
</div>
|
|
24
|
-
|
|
25
|
-
## ⚡ Démarrage
|
|
26
|
-
|
|
27
|
-
**Prérequis** : Node.js 20+, Salesforce CLI (`sf`), Chrome ou Edge, un agent IA. `doctor` vérifie tout et installe ce qu'il peut.
|
|
28
|
-
|
|
29
|
-
À la racine de votre projet Salesforce DX (ou dans un dossier vide) :
|
|
30
|
-
|
|
31
|
-
```bash
|
|
32
|
-
npx tadforce doctor
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
Puis, dans votre agent IA (Claude Code, Codex, Cursor, Copilot…) :
|
|
36
|
-
|
|
37
|
-
```text
|
|
38
|
-
/tadforce
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
| Chemin | Commandes |
|
|
42
|
-
|---|---|
|
|
43
|
-
| Plugin Claude Code | `/plugin marketplace add azamiji/TADForce` puis `/plugin install tadforce@tadforce` |
|
|
44
|
-
| Tout agent (Agent Skills) | `npx tadforce doctor` installe les skills dans `.claude/`, `.agents/`, `.cursor/`, `.codex/`… |
|
|
45
|
-
| Déterministe / CI | `npx tadforce prepare --org prod`, puis `npx tadforce build --pdf --docx`, puis `npx tadforce validate` |
|
|
46
|
-
|
|
47
|
-
## 🧭 Quatre commandes
|
|
48
|
-
|
|
49
|
-
| Commande | Ce qu'elle fait | Votre part |
|
|
50
|
-
|---|---|---|
|
|
51
|
-
| `npx tadforce doctor` | Tout ce qui précède la rédaction, en boucle jusqu'à ce que tout soit vert : outils, projet SFDX, connexion à l'org (réutilise la `target-org` du projet, n'ouvre le navigateur que si nécessaire), métadonnées (récupérées depuis l'org avec un manifest `*`, ou vos sources locales), logo et couleurs du client, puis les questions dont seul un humain connaît la réponse | Vous connecter une fois ; valider les réponses recommandées ; nommer les systèmes externes ; confirmer ce qui est inutilisé ou décommissionné |
|
|
52
|
-
| `npx tadforce build` | Votre agent rédige chaque section à partir de sa fiche et des faits, puis le document est produit : **HTML** toujours, **PDF** et **Word** à la demande, et validé | — |
|
|
53
|
-
| `npx tadforce review` | Ouvre le document pour le commenter. **Enregistrer écrit dans le même fichier** : un seul fichier par document, pas de copies, aucun commentaire perdu. Puis `/tadforce-review` transforme chaque commentaire en correction | Commenter un passage ou capturer une zone, Enregistrer |
|
|
54
|
-
| `npx tadforce update` | Quelques mois plus tard : relit l'org, archive les faits précédents, liste les sections touchées par ce qui a changé (une classe Apex ajoutée, un Flow modifié, de nouveaux chiffres…), incrémente la version. `/tadforce-update` ne réécrit que ces sections | — |
|
|
55
|
-
|
|
56
|
-
<div align="center">
|
|
57
|
-
<img src="assets/gifs/doctor.gif" alt="tadforce doctor pose les questions dont seul un humain connaît la réponse" width="80%">
|
|
58
|
-
</div>
|
|
59
|
-
|
|
60
|
-
Les commandes fines (`manifest`, `retrieve`, `facts`, `next`, `validate`…) restent disponibles pour la CI et les experts : `npx tadforce help --all`.
|
|
61
|
-
|
|
62
|
-
## 📦 Ce que vous obtenez
|
|
63
|
-
|
|
64
|
-
- **Document d'architecture technique**, destiné au client :
|
|
65
|
-
- en HTML : navigable, modifiable en place, avec commentaire d'un passage ou capture d'une zone, enregistrement dans le même fichier et export PDF ou Word ;
|
|
66
|
-
- en PDF A4 et en DOCX, la version dans le nom du fichier.
|
|
67
|
-
- **Axes d'amélioration**, document interne à l'intégrateur : signaux mesurés par pilier Well-Architected, registre `AX-nn` et feuille de route. Ils restent hors du document client.
|
|
68
|
-
- **`facts.json`** : l'inventaire déterministe de l'org, identique octet pour octet d'une exécution à l'autre.
|
|
69
|
-
|
|
70
|
-
Le document porte **le logo et les couleurs du client** : le logo est cherché dans l'org (static resources, content assets, assets des sites) ou fourni par vous ; les couleurs viennent du thème Lightning de l'org, sinon du logo, toujours lisibles (contraste WCAG vérifié). L'intégrateur et l'auteur sont facultatifs : laissés vides, le document ne nomme que le client.
|
|
71
|
-
|
|
72
|
-
<div align="center">
|
|
73
|
-
<img src="assets/gifs/review.gif" alt="Commenter un passage dans le HTML et l'enregistrer dans le même fichier" width="92%">
|
|
74
|
-
</div>
|
|
75
|
-
|
|
76
|
-
## 🧩 Tous les types de projets Salesforce
|
|
77
|
-
|
|
78
|
-
| Votre situation | Comment TADForce s'y adapte |
|
|
79
|
-
|---|---|
|
|
80
|
-
| **Une org complète**, état existant | `doctor` récupère tout avec un manifest `*` maîtrisé (130 types, rapports et tableaux de bord dans leurs dossiers, objets standard) |
|
|
81
|
-
| **Un projet dans une org partagée** (plusieurs BU, plusieurs intégrateurs) | Périmètre : `--scope "NWB_*,Loan*"` ou `--package-dirs`. Les composants hors périmètre sortent des faits, des chiffres et des tableaux ; les objets standard et les réglages globaux restent |
|
|
82
|
-
| **Avant ou pendant le build** | Fournissez vos spécifications fonctionnelles (`--specs SFD.pdf,…`) : elles deviennent des preuves (niveau P2/P3) à côté des métadonnées |
|
|
83
|
-
| **Sans accès à l'org**, sources seules | `--source local` documente les package directories du projet |
|
|
84
|
-
| **Packages unlocked ou managés** | Namespaces et packages installés sont détectés et documentés avec leurs preuves |
|
|
85
|
-
| **Tous les clouds, toutes les industries** | Une section ne s'active que si le produit est détecté, avec sa fiche de rédaction |
|
|
86
|
-
|
|
87
|
-
- **Clouds** : Sales, Service, Field Service, Experience, Marketing, Commerce, CPQ et Revenue ;
|
|
88
|
-
- **Industries** : Financial Services, Insurance, Health, Life Sciences, Manufacturing, Automotive, Consumer Goods, Communications, Public Sector, Education, Nonprofit, Loyalty, Net Zero ;
|
|
89
|
-
- **Plateforme** : OmniStudio, Data Cloud, Agentforce, CRM Analytics, Platform Events, Shield, MuleSoft, Heroku et Slack.
|
|
90
|
-
|
|
91
|
-
## 🔒 Lecture seule par construction
|
|
92
|
-
|
|
93
|
-
- **Liste blanche des commandes `sf`** : pas de deploy, pas de suppression, pas d'exécution d'Apex.
|
|
94
|
-
- **Garde-fou SOQL** : seuls les objets de configuration de l'org et les agrégats sont lus, jamais les données métier.
|
|
95
|
-
- **`force-app` jamais modifié** : la récupération se fait dans `tadforce/.snapshot`.
|
|
96
|
-
- **Aucun secret dans le texte** : la validation bloque les secrets et les identifiants d'org.
|
|
97
|
-
|
|
98
|
-
**Ce qui sort de votre poste** : le CLI TADForce n'envoie rien nulle part — ni télémétrie, ni serveur, ni compte. Il parle à votre org via votre propre Salesforce CLI, et au web seulement quand **vous** lui donnez l'URL d'un logo. Votre agent IA lit les métadonnées, les faits et les spécifications que vous fournissez pour rédiger, exactement comme lorsque vous lui faites lire votre code. Utilisez l'agent et les réglages de confidentialité validés par votre entreprise pour les travaux client.
|
|
99
|
-
|
|
100
|
-
## 🎯 Reproductible
|
|
101
|
-
|
|
102
|
-
- **Les chiffres sont des placeholders**, par exemple `{{n:apex.classes}}`.
|
|
103
|
-
- **Les tableaux, les ERD, la hiérarchie des rôles et les annexes sont générés.**
|
|
104
|
-
- **84 fiches de section** cadrent la rédaction de n'importe quel agent.
|
|
105
|
-
- **`npx tadforce validate`** bloque tout ce qui ne doit pas être livré.
|
|
106
|
-
|
|
107
|
-
## 👤 Auteur
|
|
108
|
-
|
|
109
|
-
<table>
|
|
110
|
-
<tr>
|
|
111
|
-
<td width="110"><img src="https://github.com/azamiji.png" width="96" alt="Jihad AZAMI HASSANI"></td>
|
|
112
|
-
<td>
|
|
113
|
-
|
|
114
|
-
**Jihad AZAMI HASSANI**<br>
|
|
115
|
-
Salesforce Technical Architect, 10+ ans d'expérience · Claude Certified Architect · Leader du Salesforce Architect Community Group de Casablanca<br>
|
|
116
|
-
[](https://www.linkedin.com/in/jihadazami/)<br>
|
|
117
|
-
[LinkedIn](https://www.linkedin.com/in/jihadazami/) · [GitHub](https://github.com/azamiji)
|
|
118
|
-
|
|
119
|
-
</td>
|
|
120
|
-
</tr>
|
|
121
|
-
</table>
|
|
122
|
-
|
|
123
|
-
## ⚖️ Licence
|
|
124
|
-
|
|
125
|
-
[Apache-2.0](LICENSE). Salesforce et les noms de ses produits sont des marques de Salesforce, Inc. TADForce est un projet communautaire indépendant, ni affilié à Salesforce ni approuvé par elle. L'organisation de l'exemple (Northwind Bank, Acme Integrators) est fictive.
|