@enformik/cli 0.5.0 → 0.6.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.
@@ -1,7 +1,7 @@
1
1
  import { CliError } from '../errors.js';
2
2
  import { assertSecureUrl, joinUrl, request } from '../http.js';
3
3
  import { DeployClient } from '../site.js';
4
- import { prepare } from './schema.js';
4
+ import { assertExpectedAppHost, prepare } from './schema.js';
5
5
  const ICON = { ok: '✓', warn: '!', fail: '✗', skip: '-' };
6
6
  /** Web kullanıcısına (external) açık sayılan kural: rol "external" ya da özel rol; en az bir okuma eylemi. */
7
7
  function grantsWebRead(r) {
@@ -14,9 +14,9 @@ function grantsWebRead(r) {
14
14
  return null;
15
15
  }
16
16
  /**
17
- * enformik app check [--entity a,b] — web uygulamasının Enformik'le entegrasyon sağlığını SALT OKUNUR denetler.
18
- * Site tarafı (ENFORMIK_CONSOLE_URL + ENFORMIK_DEPLOY_KEY) ve uygulama tarafı (ENFORMIK_APP_URL + istemci) bağımsızdır;
19
- * kimliği olmayan taraf atlanır. Çıkış: engelleyici sorun 1, yalnız uyarı 2, hepsi tamam 0.
17
+ * enformik app check [--entity a,b] — READ-ONLY check of the web app's integration health with Enformik.
18
+ * The site side (ENFORMIK_CONSOLE_URL + ENFORMIK_DEPLOY_KEY) and the app side (ENFORMIK_APP_URL + client) are independent;
19
+ * a side without credentials is skipped. Exit: blocking problem 1, warnings only 2, all OK 0.
20
20
  */
21
21
  export async function appCheck(ctx) {
22
22
  const { settings, out } = ctx;
@@ -24,17 +24,23 @@ export async function appCheck(ctx) {
24
24
  const hasSite = !!(settings.consoleUrl && settings.deployKey);
25
25
  const hasApp = !!(settings.appUrl && settings.clientId && settings.clientSecret);
26
26
  if (!hasSite && !hasApp) {
27
- throw new CliError('Denetlenecek kimlik yok. Site için ENFORMIK_CONSOLE_URL + ENFORMIK_DEPLOY_KEY, uygulama için ENFORMIK_APP_URL + ENFORMIK_CLIENT_ID/SECRET tanımlayın.');
27
+ throw new CliError('No credentials to check. Set ENFORMIK_CONSOLE_URL + ENFORMIK_DEPLOY_KEY for the site, and ENFORMIK_APP_URL + ENFORMIK_CLIENT_ID/SECRET for the app.');
28
28
  }
29
+ // --expect-host is about the APP host (as in the schema commands); the site half ignores it. Checked first, so a mismatch stops the whole
30
+ // command (a hard error, not a check entry) before ANY request is sent, including the site half's GET /site.
31
+ if (settings.appUrl && typeof ctx.flags['expect-host'] === 'string')
32
+ assertExpectedAppHost(ctx, assertSecureUrl(settings.appUrl, 'App URL'));
29
33
  // ── Site: yayınlanmış çalışma zamanı yapılandırması ──────────────────────────────────────────
30
34
  if (hasSite) {
35
+ if (typeof ctx.flags['expect-host'] === 'string')
36
+ out.warn('Note: --expect-host checks the app host only; the site side is not checked against it.');
31
37
  try {
32
38
  const info = await new DeployClient({ consoleUrl: settings.consoleUrl, deployKey: settings.deployKey, version: ctx.version, fetchImpl: ctx.fetchImpl }).getSite();
33
39
  if (!info.live) {
34
- checks.push({ id: 'site.live', status: 'warn', message: 'Sitede henüz canlı dağıtım yok; giriş yapılandırması denetlenemedi.', hint: '`enformik site deploy` ile yayınlayın.' });
40
+ checks.push({ id: 'site.live', status: 'warn', message: 'The site has no live deployment yet; the sign-in configuration could not be checked.', hint: 'Publish it with `enformik site deploy`.' });
35
41
  }
36
42
  else {
37
- assertSecureUrl(info.url, 'Site adresi');
43
+ assertSecureUrl(info.url, 'Site URL');
38
44
  let cfg;
39
45
  try {
40
46
  const res = await request(joinUrl(info.url, '/_enformik/config.json'), { retries: 1, fetchImpl: ctx.fetchImpl });
@@ -43,29 +49,29 @@ export async function appCheck(ctx) {
43
49
  }
44
50
  catch { /* ulaşılamadı → aşağıda uyarı */ }
45
51
  if (!cfg) {
46
- checks.push({ id: 'site.config', status: 'warn', message: `${info.url}/_enformik/config.json okunamadı.`, hint: 'Site yayında mı? Yeniden dağıtmayı deneyin.' });
52
+ checks.push({ id: 'site.config', status: 'warn', message: `Could not read ${info.url}/_enformik/config.json.`, hint: 'Is the site live? Try redeploying.' });
47
53
  }
48
54
  else {
49
55
  const auth = cfg.auth;
50
56
  if (!auth || !auth.clientId) {
51
- checks.push({ id: 'site.auth', status: 'fail', message: 'Site girişi kapalı: yayınlanmış yapılandırmada giriş (auth) bilgisi yok.', hint: 'Konsol → Geliştirici sitesi → Site girişi sekmesinden girişi açıp siteyi yeniden dağıtın.' });
57
+ checks.push({ id: 'site.auth', status: 'fail', message: 'Site sign-in is off: the published configuration has no sign-in (auth) information.', hint: 'Enable sign-in under Console → Developer site → Site sign-in, then redeploy the site.' });
52
58
  }
53
59
  else {
54
- checks.push({ id: 'site.auth', status: 'ok', message: 'Site girişi açık; yayınlanmış yapılandırmada giriş bilgisi var.' });
60
+ checks.push({ id: 'site.auth', status: 'ok', message: 'Site sign-in is on; the published configuration has sign-in information.' });
55
61
  }
56
62
  const ok = typeof cfg.appUrl === 'string' && typeof cfg.apiUrl === 'string' && !!cfg.workspaceId;
57
- checks.push(ok ? { id: 'site.binding', status: 'ok', message: 'Site bir uygulamaya ve çalışma alanına bağlı.' }
58
- : { id: 'site.binding', status: 'fail', message: 'Yapılandırmada uygulama/çalışma alanı bağlantısı eksik.', hint: 'Konsolda geliştirici sitesinin uygulama bağlantısını kontrol edin.' });
63
+ checks.push(ok ? { id: 'site.binding', status: 'ok', message: 'The site is linked to an app and a workspace.' }
64
+ : { id: 'site.binding', status: 'fail', message: 'The configuration is missing the app/workspace link.', hint: 'Check the developer site\'s app link in the console.' });
59
65
  }
60
66
  }
61
67
  }
62
68
  catch (e) {
63
69
  // Bir yarının hatası diğer yarıyı ve checks[] çıktısını kaybettirmesin.
64
- checks.push({ id: 'site.error', status: 'fail', message: `Site denetimi tamamlanamadı: ${e.message}` });
70
+ checks.push({ id: 'site.error', status: 'fail', message: `The site check could not be completed: ${e.message}` });
65
71
  }
66
72
  }
67
73
  else {
68
- checks.push({ id: 'site', status: 'skip', message: 'Site denetimi atlandı (ENFORMIK_CONSOLE_URL + ENFORMIK_DEPLOY_KEY yok).' });
74
+ checks.push({ id: 'site', status: 'skip', message: 'Site check skipped (ENFORMIK_CONSOLE_URL + ENFORMIK_DEPLOY_KEY not set).' });
69
75
  }
70
76
  // ── Uygulama: modül, erişim modu ve web kullanıcısı erişimi ──────────────────────────────────
71
77
  if (hasApp) {
@@ -74,27 +80,27 @@ export async function appCheck(ctx) {
74
80
  const slug = p.manifest.module.slug;
75
81
  const mod = await p.client.findModule(slug);
76
82
  if (!mod) {
77
- checks.push({ id: 'module.installed', status: 'fail', message: `"${slug}" modülü uygulamada kurulu değil.`, hint: '`enformik schema install` (Şema oluşturucu) ile kurun.' });
83
+ checks.push({ id: 'module.installed', status: 'fail', message: `Module "${slug}" is not installed in the app.`, hint: 'Install it with `enformik schema install` (Schema builder).' });
78
84
  }
79
85
  else if (mod.version !== p.manifest.module.version) {
80
- checks.push({ id: 'module.version', status: 'warn', message: `Uygulamadaki modül sürümü ${mod.version}, manifest ${p.manifest.module.version}.`, hint: '`enformik schema diff` / `schema push` ile eşitleyin.' });
86
+ checks.push({ id: 'module.version', status: 'warn', message: `The module version in the app is ${mod.version}; the manifest is ${p.manifest.module.version}.`, hint: 'Sync them with `enformik schema diff` / `schema push`.' });
81
87
  }
82
88
  else {
83
- checks.push({ id: 'module.version', status: 'ok', message: `Modül ${slug} ${mod.version} kurulu ve manifestle aynı sürümde.` });
89
+ checks.push({ id: 'module.version', status: 'ok', message: `Module ${slug} ${mod.version} is installed and matches the manifest version.` });
84
90
  }
85
91
  const wanted = typeof ctx.flags.entity === 'string' && ctx.flags.entity !== ''
86
92
  ? ctx.flags.entity.split(',').map((s) => s.trim().toLowerCase()).filter(Boolean)
87
93
  : (p.manifest.entities ?? []).map((e) => (e.slug ?? '').toLowerCase()).filter(Boolean);
88
94
  const summary = await p.client.accessSummary(wanted);
89
95
  if (!summary) {
90
- checks.push({ id: 'access', status: 'warn', message: 'Bu uygulama sürümü erişim özeti ucunu desteklemiyor; web kullanıcısı erişimi denetlenemedi.', hint: 'Uygulama güncellenince yeniden deneyin.' });
96
+ checks.push({ id: 'access', status: 'warn', message: 'This app version does not support the access summary endpoint; web user access could not be checked.', hint: 'Try again once the app is updated.' });
91
97
  }
92
98
  else {
93
99
  if (summary.mode !== 'on') {
94
- checks.push({ id: 'access.mode', status: 'fail', message: `Kayıt erişim kuralları "${summary.mode || 'bilinmiyor'}" modunda; web kullanıcıları bu modda çalışmaz.`, hint: 'Etkinleştirilmesi için Enformik yönetim konsolundan destek kaydı açın.' });
100
+ checks.push({ id: 'access.mode', status: 'fail', message: `Record access rules are in "${summary.mode || 'unknown'}" mode; web users do not work in this mode.`, hint: 'Open a support ticket from the Enformik management console to have it enabled.' });
95
101
  }
96
102
  else {
97
- checks.push({ id: 'access.mode', status: 'ok', message: 'Kayıt erişim kuralları etkin.' });
103
+ checks.push({ id: 'access.mode', status: 'ok', message: 'Record access rules are enabled.' });
98
104
  }
99
105
  const byName = new Map(summary.entities.map((e) => [e.entity.toLowerCase(), e.rules]));
100
106
  const none = [];
@@ -109,25 +115,25 @@ export async function appCheck(ctx) {
109
115
  none.push(name);
110
116
  }
111
117
  if (wanted.length === 0) {
112
- checks.push({ id: 'access.web', status: 'skip', message: 'Denetlenecek entity yok (manifestte entity ya da --entity yok).' });
118
+ checks.push({ id: 'access.web', status: 'skip', message: 'No entities to check (no entities in the manifest and no --entity).' });
113
119
  }
114
120
  else if (none.length > 0) {
115
- checks.push({ id: 'access.web', status: 'warn', message: `Web kullanıcıları şu tablolarda veri göremez (okuma kuralı yok): ${none.join(', ')}.`, hint: 'Bir yönetici ilgili tabloların Kayıt Erişimi ekranında "Web Kullanıcısı" (ya da özel rol) için okuma kuralı tanımlamalı; düşük rollü istemci erişim kuralı uygulayamaz.' });
121
+ checks.push({ id: 'access.web', status: 'warn', message: `Web users cannot see data in these tables (no read rule): ${none.join(', ')}.`, hint: 'An administrator must define a read rule for "Web User" (or a custom role) on the Record Access screen of those tables; a low-role client cannot apply access rules.' });
116
122
  }
117
123
  else {
118
- checks.push({ id: 'access.web', status: 'ok', message: `Web kullanıcıları için okuma kuralı var (${wanted.length - custom.length} tablo).` });
124
+ checks.push({ id: 'access.web', status: 'ok', message: `Read rules exist for web users (${wanted.length - custom.length} tables).` });
119
125
  }
120
126
  if (custom.length > 0) {
121
- checks.push({ id: 'access.custom', status: 'warn', message: `Şu tablolarda yalnızca özel rol kuralı var: ${custom.join(', ')}.`, hint: 'Web kullanıcılarına ilgili özel rolün atandığından emin olun (yeni kullanıcı varsayılan rolü ya da yönetici ataması).' });
127
+ checks.push({ id: 'access.custom', status: 'warn', message: `These tables have only a custom role rule: ${custom.join(', ')}.`, hint: 'Make sure the relevant custom role is assigned to web users (the default role for new users, or an administrator assignment).' });
122
128
  }
123
129
  }
124
130
  }
125
131
  catch (e) {
126
- checks.push({ id: 'app.error', status: 'fail', message: `Uygulama denetimi tamamlanamadı: ${e.message}` });
132
+ checks.push({ id: 'app.error', status: 'fail', message: `The app check could not be completed: ${e.message}` });
127
133
  }
128
134
  }
129
135
  else {
130
- checks.push({ id: 'app', status: 'skip', message: 'Uygulama denetimi atlandı (ENFORMIK_APP_URL + ENFORMIK_CLIENT_ID/SECRET yok).' });
136
+ checks.push({ id: 'app', status: 'skip', message: 'App check skipped (ENFORMIK_APP_URL + ENFORMIK_CLIENT_ID/SECRET not set).' });
131
137
  }
132
138
  for (const c of checks) {
133
139
  const paint = c.status === 'ok' ? 'green' : c.status === 'fail' ? 'red' : c.status === 'warn' ? 'yellow' : 'dim';
@@ -5,7 +5,7 @@ import { CliError } from '../errors.js';
5
5
  import { parseManifest } from '../schema.js';
6
6
  /**
7
7
  * enformik codegen [--manifest module.json] [--out src/enformik.generated.ts] [--check]
8
- * Yerel manifestten TypeScript tipleri + tipli veri istemcisi üretir; ağ/kimlik gerekmez. --check: dosya güncel değilse çıkış 2.
8
+ * Generates TypeScript types + a typed data client from the local manifest; no network or credentials needed. --check: exit 2 if the file is out of date.
9
9
  */
10
10
  export async function codegen(ctx) {
11
11
  const manifestPath = resolve(ctx.cwd, ctx.settings.manifestPath);
@@ -14,7 +14,7 @@ export async function codegen(ctx) {
14
14
  text = readFileSync(manifestPath, 'utf8');
15
15
  }
16
16
  catch {
17
- throw new CliError(`Manifest okunamadı: ${manifestPath}`);
17
+ throw new CliError(`Could not read the manifest: ${manifestPath}`);
18
18
  }
19
19
  const manifest = parseManifest(text, ctx.settings.manifestPath);
20
20
  const code = generateClient(manifest);
@@ -26,7 +26,7 @@ export async function codegen(ctx) {
26
26
  const slug = manifest.module.slug;
27
27
  if (ctx.flags.check === true) {
28
28
  const upToDate = same;
29
- ctx.out.info(upToDate ? `✓ ${ctx.settings.codegenOut} güncel (${slug}).` : `✗ ${ctx.settings.codegenOut} ${current === null ? 'yok' : 'manifestle uyumsuz'}; \`enformik codegen\` ile yeniden üretin.`);
29
+ ctx.out.info(upToDate ? `✓ ${ctx.settings.codegenOut} up to date (${slug}).` : `✗ ${ctx.settings.codegenOut} ${current === null ? 'is missing' : 'does not match the manifest'}; regenerate it with \`enformik codegen\`.`);
30
30
  ctx.out.result({ module: slug, out: ctx.settings.codegenOut, upToDate });
31
31
  return upToDate ? 0 : 2;
32
32
  }
@@ -36,7 +36,7 @@ export async function codegen(ctx) {
36
36
  writeFileSync(outPath, code);
37
37
  }
38
38
  const n = (code.match(/^export interface \w+Input /gm) ?? []).length;
39
- ctx.out.info(changed ? `✓ ${ctx.settings.codegenOut} yazıldı (${slug}, ${n} entity).` : `= ${ctx.settings.codegenOut} zaten güncel (${slug}, ${n} entity).`);
39
+ ctx.out.info(changed ? `✓ ${ctx.settings.codegenOut} written (${slug}, ${n} entities).` : `= ${ctx.settings.codegenOut} already up to date (${slug}, ${n} entities).`);
40
40
  ctx.out.result({ module: slug, out: ctx.settings.codegenOut, entities: n, written: changed });
41
41
  return 0;
42
42
  }
@@ -15,10 +15,10 @@ export async function link(ctx) {
15
15
  return cur;
16
16
  return (await ctx.ask(label, cur)) || undefined;
17
17
  };
18
- next.consoleUrl = await prompt('Konsol adresi (site dağıtımı için; boş bırakılabilir)', config.consoleUrl, str('console-url'));
19
- next.appUrl = await prompt('Uygulama adresi (şema yükseltme için; boş bırakılabilir)', config.appUrl, str('app-url'));
20
- next.workspaceId = await prompt('Çalışma alanı kimliği (isteğe bağlı)', config.workspaceId, str('workspace'));
21
- const slug = await prompt('Modül slug (şema yükseltme için)', config.module?.slug, str('module'));
18
+ next.consoleUrl = await prompt('Console URL (for site deployment; may be left empty)', config.consoleUrl, str('console-url'));
19
+ next.appUrl = await prompt('App URL (for schema upgrades; may be left empty)', config.appUrl, str('app-url'));
20
+ next.workspaceId = await prompt('Workspace ID (optional)', config.workspaceId, str('workspace'));
21
+ const slug = await prompt('Module slug (for schema upgrades)', config.module?.slug, str('module'));
22
22
  const manifest = str('manifest') ?? config.module?.manifest;
23
23
  const dir = str('dir') ?? config.site?.dir;
24
24
  if (slug || manifest)
@@ -26,26 +26,26 @@ export async function link(ctx) {
26
26
  if (dir)
27
27
  next.site = { ...(config.site ?? {}), dir };
28
28
  if (!next.consoleUrl && !next.appUrl)
29
- throw new CliError('En az bir adres gerekli: --console-url (site dağıtımı) ve/ya da --app-url (şema yükseltme).');
29
+ throw new CliError('At least one URL is required: --console-url (site deployment) and/or --app-url (schema upgrades).');
30
30
  if (next.consoleUrl)
31
- assertSecureUrl(next.consoleUrl, 'Konsol adresi');
31
+ assertSecureUrl(next.consoleUrl, 'Console URL');
32
32
  if (next.appUrl) {
33
- assertSecureUrl(next.appUrl, 'Uygulama adresi');
33
+ assertSecureUrl(next.appUrl, 'App URL');
34
34
  if (flags['no-verify'] !== true) {
35
35
  await discoverApp(next.appUrl, ctx.version, ctx.fetchImpl); // erişilebilir mi / API istemcisi kabul ediyor mu
36
- out.info('✓ Uygulama erişilebilir.');
36
+ out.info('✓ The app is reachable.');
37
37
  }
38
38
  }
39
39
  for (const k of Object.keys(next))
40
40
  if (next[k] === undefined)
41
41
  delete next[k];
42
42
  const path = saveConfig(root, next);
43
- out.info(`Yapılandırma yazıldı: ${path}`);
44
- out.info('Sırlar dosyaya yazılmaz; ortam değişkeni olarak verin:');
43
+ out.info(`Config written: ${path}`);
44
+ out.info('Secrets are not written to the file; provide them as environment variables:');
45
45
  if (next.consoleUrl)
46
- out.info(' ENFORMIK_DEPLOY_KEY (site dağıtımı: dağıtım anahtarı)');
46
+ out.info(' ENFORMIK_DEPLOY_KEY (site deployment: deploy key)');
47
47
  if (next.appUrl)
48
- out.info(' ENFORMIK_CLIENT_ID / ENFORMIK_CLIENT_SECRET (şema yükseltme: API istemcisi)');
48
+ out.info(' ENFORMIK_CLIENT_ID / ENFORMIK_CLIENT_SECRET (schema upgrades: API client)');
49
49
  out.result({ linked: true, path, config: next });
50
50
  return 0;
51
51
  }
@@ -5,45 +5,49 @@ import { CliError } from '../errors.js';
5
5
  import { assertSecureUrl } from '../http.js';
6
6
  import { adminOnlySections, parseManifest, pendingChanges, renderDiff, SchemaClient, stripBom, typeChanges, unapprovedChanges } from '../schema.js';
7
7
  import { renderTable } from '../output.js';
8
+ /** --expect-host: the app's host name must match (case-insensitive); checked before any request so nothing reaches the wrong app. */
9
+ export function assertExpectedAppHost(ctx, appUrl) {
10
+ const expect = ctx.flags['expect-host'];
11
+ if (typeof expect === 'string' && expect.toLowerCase() !== appUrl.hostname.toLowerCase()) {
12
+ throw new CliError(`Expected app "${expect}" but the target is "${appUrl.hostname}". Stopped so that nothing is applied to the wrong app.`);
13
+ }
14
+ }
8
15
  /** Ortak hazırlık: manifest → uygulama keşfi → belirteç → modül kimliği. */
9
16
  export async function prepare(ctx, mode = 'existing') {
10
17
  const { settings, out } = ctx;
11
18
  if (!settings.appUrl)
12
- throw new CliError('Uygulama adresi yok. `enformik link --app-url https://…` çalıştırın ya da ENFORMIK_APP_URL tanımlayın.');
19
+ throw new CliError('No app URL. Run `enformik link --app-url https://…` or set ENFORMIK_APP_URL.');
13
20
  if (!settings.clientId || !settings.clientSecret) {
14
- throw new CliError('ENFORMIK_CLIENT_ID ve ENFORMIK_CLIENT_SECRET tanımlı olmalı (konsolda "API anahtarları" sayfasından rolü "Şema yükseltici" — ilk kurulum için "Şema oluşturucu" — olan bir istemci oluşturun).');
15
- }
16
- const appUrl = assertSecureUrl(settings.appUrl, 'Uygulama adresi');
17
- const expect = ctx.flags['expect-host'];
18
- if (typeof expect === 'string' && expect.toLowerCase() !== appUrl.hostname.toLowerCase()) {
19
- throw new CliError(`Beklenen uygulama "${expect}" ama hedef "${appUrl.hostname}". Yanlış uygulamaya işlem yapılmaması için durduruldu.`);
21
+ throw new CliError('ENFORMIK_CLIENT_ID and ENFORMIK_CLIENT_SECRET must be set (in the console, create a client with the "Schema upgrader" role — or "Schema builder" for the first installation — on the "API keys" page).');
20
22
  }
23
+ const appUrl = assertSecureUrl(settings.appUrl, 'App URL');
24
+ assertExpectedAppHost(ctx, appUrl);
21
25
  const manifestPath = resolve(ctx.cwd, settings.manifestPath);
22
26
  let text;
23
27
  try {
24
28
  text = stripBom(readFileSync(manifestPath, 'utf8'));
25
29
  }
26
30
  catch {
27
- throw new CliError(`Manifest okunamadı: ${manifestPath}`);
31
+ throw new CliError(`Could not read the manifest: ${manifestPath}`);
28
32
  }
29
33
  const manifest = parseManifest(text, settings.manifestPath);
30
34
  // Sunucuya dosyanın kendi metni gider (JSON.stringify yeniden serileştirmesi büyük sayıları/biçimi değiştirebilirdi).
31
35
  const manifestJson = text;
32
36
  const slug = settings.moduleSlug ?? manifest.module.slug;
33
37
  if (settings.moduleSlug && settings.moduleSlug !== manifest.module.slug) {
34
- throw new CliError(`Yapılandırmadaki modül ("${settings.moduleSlug}") ile manifestteki slug ("${manifest.module.slug}") farklı.`);
38
+ throw new CliError(`The module in the config ("${settings.moduleSlug}") differs from the slug in the manifest ("${manifest.module.slug}").`);
35
39
  }
36
40
  const seedData = ctx.flags['seed-data'] === true;
37
41
  const admin = adminOnlySections(manifest, { seedData });
38
42
  if (admin.length > 0) {
39
- out.warn(`Not: manifest yönetici gerektiren bölümler içeriyor (${admin.join(', ')}). "Şema yükseltici/oluşturucu" rolündeki bir istemci bunu uygulayamaz.`);
43
+ out.warn(`Note: the manifest contains sections that require an administrator (${admin.join(', ')}). A client with the "Schema upgrader/builder" role cannot apply them.`);
40
44
  }
41
45
  const app = await discoverApp(settings.appUrl, ctx.version, ctx.fetchImpl);
42
46
  const workspaceId = settings.workspaceId || app.tenantId;
43
47
  if (!workspaceId)
44
- throw new CliError('Çalışma alanı belirlenemedi. --workspace <kimlik> verin ya da `enformik link --workspace` ile kaydedin.');
48
+ throw new CliError('Could not determine the workspace. Pass --workspace <id> or save it with `enformik link --workspace`.');
45
49
  // Token adresi uygulamanın /api/config yanıtından gelir; istemci sırrı oraya gider, bu yüzden hedefi görünür kıl.
46
- out.info(`Kimlik sağlayıcı: ${new URL(app.oidcAuthority).host} (istemci kimliği buraya gönderilir)`);
50
+ out.info(`Identity provider: ${new URL(app.oidcAuthority).host} (the client credentials are sent here)`);
47
51
  const tok = await fetchAccessToken(app, settings.clientId, settings.clientSecret, ctx.version, ctx.fetchImpl);
48
52
  const client = new SchemaClient({ appUrl: settings.appUrl, token: tok.token, workspaceId, version: ctx.version, fetchImpl: ctx.fetchImpl });
49
53
  if (mode === 'validate') {
@@ -52,7 +56,7 @@ export async function prepare(ctx, mode = 'existing') {
52
56
  if (mode === 'install') {
53
57
  const existing = await client.findModule(slug);
54
58
  if (existing)
55
- throw new CliError(`"${slug}" modülü bu uygulamada zaten kurulu (sürüm ${existing.version}). Yükseltmek için \`enformik schema push\` kullanın.`);
59
+ throw new CliError(`Module "${slug}" is already installed in this app (version ${existing.version}). Use \`enformik schema push\` to upgrade it.`);
56
60
  return { client, manifest, manifestJson, moduleId: '', installedVersion: '', appHost: appUrl.hostname, seedData };
57
61
  }
58
62
  const mod = await client.resolveModule(slug);
@@ -73,7 +77,7 @@ function summary(p, r) {
73
77
  export async function schemaDiff(ctx) {
74
78
  const p = await prepare(ctx);
75
79
  const r = await p.client.diff(p.moduleId, p.manifestJson, { seedData: p.seedData });
76
- ctx.out.info(`Modül: ${p.manifest.module.slug} kurulu ${p.installedVersion} → manifest ${p.manifest.module.version}`);
80
+ ctx.out.info(`Module: ${p.manifest.module.slug} installed ${p.installedVersion} → manifest ${p.manifest.module.version}`);
77
81
  ctx.out.info(renderDiff(ctx.out, r, { appHost: p.appHost }));
78
82
  const s = summary(p, r);
79
83
  ctx.out.result(s);
@@ -87,7 +91,7 @@ export async function schemaDiff(ctx) {
87
91
  export async function schemaPush(ctx) {
88
92
  const p = await prepare(ctx);
89
93
  const dry = await p.client.diff(p.moduleId, p.manifestJson, { seedData: p.seedData });
90
- ctx.out.info(`Modül: ${p.manifest.module.slug} kurulu ${p.installedVersion} → manifest ${p.manifest.module.version}`);
94
+ ctx.out.info(`Module: ${p.manifest.module.slug} installed ${p.installedVersion} → manifest ${p.manifest.module.version}`);
91
95
  ctx.out.info(renderDiff(ctx.out, dry, { appHost: p.appHost }));
92
96
  if (dry.errors.length > 0) {
93
97
  ctx.out.result({ ...summary(p, dry), applied: false });
@@ -96,18 +100,18 @@ export async function schemaPush(ctx) {
96
100
  const pending = pendingChanges(dry.diffs);
97
101
  // Sürüm farkı olsa bile yapısal değişiklik yoksa uygulanacak bir şey yok.
98
102
  if (pending.length === 0) {
99
- ctx.out.info('Uygulanacak değişiklik yok.');
103
+ ctx.out.info('No changes to apply.');
100
104
  ctx.out.result({ ...summary(p, dry), applied: false });
101
105
  return 0;
102
106
  }
103
107
  const tc = typeChanges(dry.diffs);
104
108
  if (tc.length > 0)
105
- ctx.out.warn(`Alan türü değişiyor (${tc.map((d) => d.slug).join(', ')}): bu işlem yalnızca yönetici rolüyle uygulanabilir.`);
109
+ ctx.out.warn(`Field type is changing (${tc.map((d) => d.slug).join(', ')}): this can only be applied with the administrator role.`);
106
110
  if (ctx.flags.yes !== true) {
107
111
  if (!ctx.isTTY)
108
- throw new CliError('Etkileşimli terminal yok: değişiklikleri uygulamak için --yes verin (sessizce onaylanmaz).');
109
- if (!(await ctx.confirm('Değişiklikleri uygulamak istiyor musunuz?'))) {
110
- ctx.out.info('Vazgeçildi; hiçbir değişiklik uygulanmadı.');
112
+ throw new CliError('No interactive terminal: pass --yes to apply the changes (nothing is confirmed silently).');
113
+ if (!(await ctx.confirm('Do you want to apply the changes?'))) {
114
+ ctx.out.info('Cancelled; no changes were applied.');
111
115
  ctx.out.result({ ...summary(p, dry), applied: false });
112
116
  return 1;
113
117
  }
@@ -115,27 +119,27 @@ export async function schemaPush(ctx) {
115
119
  const res = await p.client.upgrade(p.moduleId, p.manifestJson, { seedData: p.seedData });
116
120
  if (res.errors.length > 0) {
117
121
  for (const e of res.errors)
118
- ctx.out.error(`Hata: ${e}`);
122
+ ctx.out.error(`Error: ${e}`);
119
123
  ctx.out.result({ ...summary(p, res), applied: false });
120
124
  return 1;
121
125
  }
122
- ctx.out.info(ctx.out.paint('green', `✓ Uygulandı: ${pendingChanges(res.diffs).length || pending.length} değişiklik (modül ${p.manifest.module.slug} ${p.manifest.module.version}).`));
126
+ ctx.out.info(ctx.out.paint('green', `✓ Applied: ${pendingChanges(res.diffs).length || pending.length} changes (module ${p.manifest.module.slug} ${p.manifest.module.version}).`));
123
127
  // Onay ile uygulama arasında uygulama başkası tarafından değişmiş olabilir: onaylanmayan bir değişiklik uygulandıysa açıkça bildir.
124
128
  const extra = unapprovedChanges(dry.diffs, res.diffs);
125
129
  if (extra.length > 0) {
126
- ctx.out.error(`Uyarı: onayladığınız farkta olmayan ${extra.length} değişiklik de uygulandı (diff ile upgrade arasında uygulama değişmiş olabilir): ${extra.map((d) => `${d.resourceType} ${d.slug} (${d.action})`).join(', ')}. Sonucu \`enformik schema diff\` ile doğrulayın.`);
130
+ ctx.out.error(`Warning: ${extra.length} change(s) that were not in the diff you approved were also applied (the app may have changed between diff and upgrade): ${extra.map((d) => `${d.resourceType} ${d.slug} (${d.action})`).join(', ')}. Verify the result with \`enformik schema diff\`.`);
127
131
  ctx.out.result({ ...summary(p, res), applied: true, diverged: true, unapproved: extra });
128
132
  return 1;
129
133
  }
130
134
  ctx.out.result({ ...summary(p, res), applied: true });
131
135
  return 0;
132
136
  }
133
- /** enformik schema install [--yes] [--dry-run] — modülün ilk kurulumu (rol: Şema oluşturucu ya da Yönetici). */
137
+ /** enformik schema install [--yes] [--dry-run] — first installation of the module (role: Schema builder or Administrator). */
134
138
  export async function schemaInstall(ctx) {
135
139
  const p = await prepare(ctx, 'install');
136
140
  const slug = p.manifest.module.slug;
137
141
  const dry = await p.client.install(p.manifestJson, { seedData: p.seedData, dryRun: true });
138
- ctx.out.info(`Modül: ${slug} yeni kurulum → manifest ${p.manifest.module.version}`);
142
+ ctx.out.info(`Module: ${slug} new installation → manifest ${p.manifest.module.version}`);
139
143
  ctx.out.info(renderDiff(ctx.out, dry, { appHost: p.appHost }));
140
144
  const base = { module: slug, manifestVersion: p.manifest.module.version, changes: pendingChanges(dry.diffs).length, diffs: dry.diffs, errors: dry.errors };
141
145
  if (dry.errors.length > 0) {
@@ -148,9 +152,9 @@ export async function schemaInstall(ctx) {
148
152
  }
149
153
  if (ctx.flags.yes !== true) {
150
154
  if (!ctx.isTTY)
151
- throw new CliError('Etkileşimli terminal yok: modülü kurmak için --yes verin (sessizce onaylanmaz).');
152
- if (!(await ctx.confirm('Modülü kurmak istiyor musunuz?'))) {
153
- ctx.out.info('Vazgeçildi; hiçbir şey kurulmadı.');
155
+ throw new CliError('No interactive terminal: pass --yes to install the module (nothing is confirmed silently).');
156
+ if (!(await ctx.confirm('Do you want to install the module?'))) {
157
+ ctx.out.info('Cancelled; nothing was installed.');
154
158
  ctx.out.result({ ...base, installed: false });
155
159
  return 1;
156
160
  }
@@ -158,11 +162,11 @@ export async function schemaInstall(ctx) {
158
162
  const res = await p.client.install(p.manifestJson, { seedData: p.seedData, dryRun: false });
159
163
  if (res.errors.length > 0) {
160
164
  for (const e of res.errors)
161
- ctx.out.error(`Hata: ${e}`);
165
+ ctx.out.error(`Error: ${e}`);
162
166
  ctx.out.result({ ...base, diffs: res.diffs, errors: res.errors, installed: false });
163
167
  return 1;
164
168
  }
165
- ctx.out.info(ctx.out.paint('green', `✓ Kuruldu: modül ${slug} ${p.manifest.module.version} (${pendingChanges(res.diffs).length} kaynak). Sonraki değişiklikler için \`enformik schema push\` kullanın.`));
169
+ ctx.out.info(ctx.out.paint('green', `✓ Installed: module ${slug} ${p.manifest.module.version} (${pendingChanges(res.diffs).length} resources). Use \`enformik schema push\` for later changes.`));
166
170
  ctx.out.result({ ...base, moduleId: res.moduleId, diffs: res.diffs, errors: [], installed: true });
167
171
  return 0;
168
172
  }
@@ -174,7 +178,7 @@ export async function schemaInstall(ctx) {
174
178
  export async function schemaValidate(ctx) {
175
179
  const profileFlag = ctx.flags.profile;
176
180
  if (profileFlag !== undefined && profileFlag !== 'import' && profileFlag !== 'ai') {
177
- throw new CliError(`--profile "import" ya da "ai" olmalı (verilen: ${String(profileFlag)}).`);
181
+ throw new CliError(`--profile must be "import" or "ai" (got: ${String(profileFlag)}).`);
178
182
  }
179
183
  const profile = profileFlag === 'ai' ? 'ai' : 'import';
180
184
  const seedData = ctx.flags['seed-data'] === true;
@@ -184,20 +188,20 @@ export async function schemaValidate(ctx) {
184
188
  if (ctx.flags.offline === true) {
185
189
  // ai profilinin kuralları (isimlendirme/yapı) yalnızca sunucuda denetlenir; yerel denetimle "geçerli" demek yanlış güven verir.
186
190
  if (profileFlag === 'ai')
187
- throw new CliError('--offline, --profile ai ile birlikte kullanılamaz: ai profilinin kuralları yalnızca sunucuda denetlenir. --offline olmadan çalıştırın.');
191
+ throw new CliError('--offline cannot be combined with --profile ai: the ai profile rules are checked only on the server. Run without --offline.');
188
192
  const manifestPath = resolve(ctx.cwd, ctx.settings.manifestPath);
189
193
  let text;
190
194
  try {
191
195
  text = readFileSync(manifestPath, 'utf8');
192
196
  }
193
197
  catch {
194
- throw new CliError(`Manifest okunamadı: ${manifestPath}`);
198
+ throw new CliError(`Could not read the manifest: ${manifestPath}`);
195
199
  }
196
200
  const manifest = parseManifest(text, ctx.settings.manifestPath); // JSON + module.slug/version
197
201
  slug = manifest.module.slug;
198
202
  result = { valid: true, profile, issues: [], adminSections: adminOnlySections(manifest, { seedData }).map((section) => ({ section })) };
199
203
  source = 'local';
200
- ctx.out.warn('Yalnızca yerel denetim yapıldı (JSON, module.slug/version, yönetici bölümleri); isimlendirme ve yapı kuralları sunucuda denetlenir.');
204
+ ctx.out.warn('Only local checks were run (JSON, module.slug/version, administrator sections); naming and structure rules are checked on the server.');
201
205
  }
202
206
  else {
203
207
  const p = await prepare(ctx, 'validate');
@@ -205,16 +209,16 @@ export async function schemaValidate(ctx) {
205
209
  result = await p.client.validate(p.manifestJson, { profile, seedData });
206
210
  source = 'server';
207
211
  }
208
- ctx.out.info(`Modül: ${slug} profil: ${profile} (${source === 'server' ? 'sunucu doğrulaması' : 'yerel'})`);
212
+ ctx.out.info(`Module: ${slug} profile: ${profile} (${source === 'server' ? 'server validation' : 'local'})`);
209
213
  if (result.issues.length > 0) {
210
- ctx.out.info(renderTable(ctx.out, ['KOD', 'AÇIKLAMA'], result.issues.map((i) => [i.code, i.message])));
214
+ ctx.out.info(renderTable(ctx.out, ['CODE', 'DESCRIPTION'], result.issues.map((i) => [i.code, i.message])));
211
215
  }
212
216
  if (result.adminSections.length > 0) {
213
- ctx.out.info(ctx.out.paint('yellow', `Yönetici adımı gerekir (düşük rollü istemci uygulayamaz): ${result.adminSections.map((s) => s.section).join(', ')}`));
217
+ ctx.out.info(ctx.out.paint('yellow', `Administrator step required (a low-role client cannot apply these): ${result.adminSections.map((s) => s.section).join(', ')}`));
214
218
  }
215
219
  ctx.out.info(result.valid
216
- ? ctx.out.paint('green', source === 'local' ? '✓ Yerel denetim geçti (sunucu kuralları denetlenmedi).' : '✓ Manifest geçerli.')
217
- : ctx.out.paint('red', `✗ ${result.issues.length} bulgu var.`));
220
+ ? ctx.out.paint('green', source === 'local' ? '✓ Local checks passed (server rules were not checked).' : '✓ Manifest is valid.')
221
+ : ctx.out.paint('red', `✗ ${result.issues.length} issue(s) found.`));
218
222
  ctx.out.result({ module: slug, source, ...result });
219
223
  return result.valid ? 0 : 1;
220
224
  }