@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.
package/README.md CHANGED
@@ -1,63 +1,104 @@
1
1
  # @enformik/cli
2
2
 
3
- Enformik geliştirici CLI'si: kendi derlediğiniz **web sitesini** yayınlar ve **uygulama modülünüzü (şema)** yükseltir. CI/CD hatlarında çalışmak için tasarlanmıştır; çalışma zamanı bağımlılığı yoktur (Node.js ≥ 20).
3
+ The Enformik developer CLI: publishes the **website** you build and upgrades your **application module (schema)**. It is designed to run in CI/CD pipelines and has no runtime dependencies (Node.js ≥ 20).
4
4
 
5
5
  ```bash
6
6
  npx @enformik/cli --help
7
- # ya da kalıcı kurulum:
8
- npm install -g @enformik/cli # komut: enformik
7
+ # or install it permanently:
8
+ npm install -g @enformik/cli # command: enformik
9
9
  ```
10
10
 
11
- ## Site dağıtımı
11
+ ## Site deployment
12
12
 
13
- Gerekenler: Enformik yönetim konsolunda web sitesi ortamınızda **Geliştirici sitesi** etkin ve bir **dağıtım anahtarı** oluşturulmuş olmalı.
13
+ Requirements: in the Enformik management console, **Developer site** must be enabled on your website environment and a **deploy key** must have been created.
14
14
 
15
15
  ```bash
16
- export ENFORMIK_CONSOLE_URL=https://<konsol-adresiniz>
17
- export ENFORMIK_DEPLOY_KEY=enf_dk_… # CI gizli değişkeni; depoya yazmayın
16
+ export ENFORMIK_CONSOLE_URL=https://<your-console-url>
17
+ export ENFORMIK_DEPLOY_KEY=enf_dk_… # CI secret variable; do not commit it to the repo
18
18
 
19
19
  npx @enformik/cli site deploy --dir dist
20
20
  ```
21
21
 
22
- `index.html` kökte mi, gizli dosya (`.env`, `.git`…) var mı, dosya türleri izinli mi (çalıştırılabilir dosya, `.zip`/`.iso` ve uzantısız dosya reddedilir), boyut/kota uygun mu gibi kurallar **yüklemeden önce** denetlenir; hatalar dosya yoluyla gösterilir. Paketteki metin dosyalarında sır bulunursa yükleme durur; yanlış alarmı proje kökündeki `.enformikignore` dosyasına (satır başına bir yol, `dizin/` öneki, joker yok) yazarak atlayabilirsiniz. `--expect-host <site-adresi>` yanlış siteye dağıtımı engeller. Başarıda yeni sürümün adresi yazdırılır.
22
+ Rules such as whether `index.html` is at the root, whether there are hidden files (`.env`, `.git`…), whether the file types are allowed (executables, `.zip`/`.iso` and files without an extension are rejected), and whether the size/quota fits are checked **before uploading**; errors are shown with the file path. If a text file in the bundle contains a secret, the upload stops; you can skip a false alarm by listing the file in `.enformikignore` in the project root (one path per line, `dir/` prefix, no wildcards). `--expect-host <site-host>` prevents deploying to the wrong site. On success the address of the new release is printed.
23
23
 
24
- ## Şema (modül) yükseltme
24
+ ## Schema (module) upgrades
25
25
 
26
- Gerekenler: uygulamanızda **Şema yükseltici** (yalnızca yükseltme), **Şema oluşturucu** (yükseltme + ilk kurulum) ya da Yönetici rolünde bir **API istemcisi** (konsol → **API Anahtarları** sayfası). Modülün ilk kurulumu `schema install` ile (Şema oluşturucu/Yönetici), sonraki yükseltmeler `schema push` ile yapılır.
26
+ Requirements: an **API client** in your application with the **Schema upgrader** role (upgrades only), the **Schema builder** role (upgrades + first installation), or the Administrator role (console → **API Keys** page). The first installation of a module is done with `schema install` (Schema builder/Administrator); later upgrades are done with `schema push`.
27
27
 
28
28
  ```bash
29
- export ENFORMIK_APP_URL=https://<uygulama-adresiniz>
29
+ export ENFORMIK_APP_URL=https://<your-app-url>
30
30
  export ENFORMIK_CLIENT_ID=apic-…
31
31
  export ENFORMIK_CLIENT_SECRET=…
32
32
 
33
- npx @enformik/cli schema install --manifest module.json --yes # modülün ilk kurulumu (--dry-run: yalnızca göster)
34
- npx @enformik/cli schema diff --manifest module.json --check # değişiklik varsa çıkış kodu 2
33
+ npx @enformik/cli schema install --manifest module.json --yes # first installation of the module (--dry-run: show only)
34
+ npx @enformik/cli schema diff --manifest module.json --check # exit code 2 if there are changes
35
35
  npx @enformik/cli schema push --manifest module.json --yes
36
36
  ```
37
37
 
38
- > **Dikkat:** modül (şema) değişikliği uygulamanın **tüm çalışma alanlarını** etkiler. Canlı uygulamaya laptop'tan değil, CI'dan ve canlıya özel bir istemci ile uygulayın.
38
+ > **Warning:** a module (schema) change affects **all workspaces** in the application. Apply it to a live application from CI with a client dedicated to production, not from a laptop.
39
39
 
40
- "Şema yükseltici" ve "Şema oluşturucu" rolleri betik, otomasyon, yetki tanımı, veri kaynağı, etkileşimli sayfa, arayüz uzantısı ve tohum veri içeren manifestleri ve alan türü değişikliklerini uygulayamaz; bunlar bir yönetici tarafından uygulanır.
40
+ The "Schema upgrader" and "Schema builder" roles cannot apply manifests that contain scripts, automations, permission definitions, data sources, interactive pages, UI extensions or seed data, nor field type changes; an administrator applies these.
41
41
 
42
- ## Komutlar
42
+ ## Commands
43
43
 
44
- | Komut | Açıklama |
44
+ | Command | Description |
45
45
  | --- | --- |
46
- | `link` | `.enformik/config.json` yazar (adresler; **sır içermez**) |
47
- | `whoami` | Hangi kimlikle, hangi hedefe bağlanıldığını gösterir |
48
- | `schema diff` | Manifesti uygulamadakiyle karşılaştırır (`--check`) |
49
- | `schema push` | Değişiklikleri uygular (etkileşimsiz ortamda `--yes` zorunlu) |
50
- | `site deploy` | Derlenmiş siteyi yayınlar (`--dir`, `--no-wait`, `--timeout`, `--expect-host`) |
51
- | `site status` | Hedef site ve canlı sürüm bilgisi (`--expect-host`) |
46
+ | `link` | Writes `.enformik/config.json` (URLs only; **contains no secrets**) |
47
+ | `whoami` | Shows which identity connects to which target |
48
+ | `ai init` | Downloads the AI agent guides into `.enformik/ai/` and adds an instruction block to `AGENTS.md` (`--lang en\|tr`, default `en`; `--offline`) |
49
+ | `app check` | Read-only check of the web app's integration health: site sign-in, module version, record access rules, web user access (`--entity`, `--expect-host`) |
50
+ | `codegen` | Generates TypeScript types and a typed data client from `module.json` (`--out`, `--check`) |
51
+ | `schema validate` | Validates the manifest with the server's rules without touching the data (`--profile ai`, `--offline`) |
52
+ | `schema diff` | Compares the manifest with the one in the application (`--check`) |
53
+ | `schema install` | Installs the module for the first time (`--dry-run`, `--yes`; role: Schema builder) |
54
+ | `schema push` | Applies changes (`--yes` is required in non-interactive environments) |
55
+ | `site deploy` | Publishes the built site (`--dir`, `--no-wait`, `--timeout`, `--expect-host`) |
56
+ | `site status` | Shows the target site and the live release (`--expect-host`) |
52
57
 
53
- Genel bayraklar: `--json` (stdout'a yalnızca JSON; insan çıktısı stderr'e), `--config`, `--debug`.
54
- Çıkış kodları: `0` başarı · `1` hata · `2` (`--check`) uygulanacak değişiklik var.
55
- `--expect-host <ad>` (şema komutları): hedef uygulamanın ana makine adı farklıysa işlemi durdurur (yanlış uygulamaya push'a karşı koruma).
58
+ Run `enformik <command> --help` for a command's usage. A mistyped command (`enformik sitee --help`) is an error, not the general help.
56
59
 
57
- ## Güvenlik
60
+ Global flags: `--json`, `--config`, `--debug`, `-h`/`--help`, `-v`/`--version`.
58
61
 
59
- - Sırlar **yalnızca ortam değişkeniyle** verilir; komut satırı bayrağı veya yapılandırma dosyası yoktur (sır içeren `.enformik/config.json` reddedilir).
60
- - Kimlik bilgileri yalnızca HTTPS ile gönderilir (`localhost` hariç); yönlendirmeler takip edilmez.
61
- - Erişim belirteci yalnızca bellekte tutulur.
62
+ `--expect-host <host>` stops the operation if the host name differs. In the schema commands and in `app check` it is the **application's** host (in `app check` it is checked before any request is sent; the site side is not checked against it); in `site deploy` and `site status` it is the **site's** host.
62
63
 
63
- Ayrıntılı rehber: Enformik geliştirici dokümantasyonunda "CLI" sayfası.
64
+ ## Exit codes
65
+
66
+ | Code | Meaning |
67
+ | --- | --- |
68
+ | `0` | Success (including no-ops, `--dry-run` and `--no-wait`) |
69
+ | `1` | Error: invalid input, validation, credentials, server error, failed deployment, a declined confirmation (a closed input counts as "no"), a rate limit |
70
+ | `2` | Not an error: `schema diff --check` found changes to apply, `codegen --check` found the file out of date, or `app check` found warnings only |
71
+
72
+ ## JSON output
73
+
74
+ With `--json`, stdout carries **only** JSON (human-readable text goes to stderr), so pipelines can parse it. Errors are JSON objects with `error` (a stable code) and `message`, for example `unknown_command`, `usage`, `no_credentials` (`whoami`, with the list of what is required), `rate_limited` (with `status: 429` and, when the server sent one, `retryAfterSeconds`; the CLI does not retry a 429 by itself) or `unexpected`. Some commands report a failed outcome as their normal result instead (a declined confirmation is `applied: false`, a failed or rejected deployment is `deployed: false` with `status`, `error` or `issues`, `app check` lists its `failures`); in every case the exit code is `1`, so branch on the exit code first and read `error` second.
75
+
76
+ ## Environment variables
77
+
78
+ | Variable | Used for |
79
+ | --- | --- |
80
+ | `ENFORMIK_CONSOLE_URL`, `ENFORMIK_DEPLOY_KEY` | Site deployment (`site deploy`, `site status`, `app check`, `whoami`) |
81
+ | `ENFORMIK_APP_URL`, `ENFORMIK_CLIENT_ID`, `ENFORMIK_CLIENT_SECRET` | Schema commands, `app check`, `whoami` |
82
+ | `ENFORMIK_WORKSPACE_ID` | Workspace to use (otherwise the app's default) |
83
+ | `ENFORMIK_DOCS_URL` | Where `ai init` downloads the guides from (default `https://help.enformik.com`) |
84
+
85
+ The URLs can also be saved with `enformik link`; precedence is flag, then environment variable, then `.enformik/config.json`. Secrets are never read from a flag or the config file.
86
+
87
+ ## Upgrading from 0.5
88
+
89
+ - All output (help, errors, generated file text) is in English; it used to be Turkish. This includes the `message` strings in `--json` output, so a parser that matched Turkish text must be updated (match the stable `error` code instead).
90
+ - `ai init --lang` now defaults to `en` (it was `tr`); pass `--lang tr` for the Turkish guide. Files of the other language already in `.enformik/ai/` are left in place.
91
+ - The confirmation prompt is `[y/N]` and accepts only `y` or `yes` (it was `[e/H]` and also accepted `e` and `evet`). If the input closes before an answer, that counts as "no": the command stops with exit code `1` instead of ending silently. A CI script that piped `e` or `evet` into `schema push` now gets exit code `1` and nothing is applied; pass `--yes` instead.
92
+ - `whoami --json` without credentials prints a `no_credentials` JSON error instead of nothing.
93
+ - A rate limit (HTTP 429) is reported as `Rate limited by the server; retry in about N s.` (and as `rate_limited` in JSON); there is no automatic retry.
94
+ - A mistyped command with `--help` (`enformik sitee --help`) is now an `unknown_command` error with exit code `1`; it used to print the general help and exit `0`.
95
+ - `app check --expect-host` checks the application host before any request is sent; the site side ignores it.
96
+ - If the reply to a deployment's `finalize` is lost, the CLI now reads the deployment's real state and carries on instead of reporting a failure.
97
+
98
+ ## Security
99
+
100
+ - Secrets are provided **only through environment variables**; there is no command-line flag or config file for them (a `.enformik/config.json` that contains a secret is rejected).
101
+ - Credentials are sent only over HTTPS (except `localhost`); redirects are not followed.
102
+ - The access token is kept in memory only.
103
+
104
+ Detailed guide: the "CLI" page in the Enformik developer documentation.
package/dist/auth.js CHANGED
@@ -1,21 +1,23 @@
1
1
  import { ApiError, CliError } from './errors.js';
2
- import { assertSecureUrl, ensureOk, joinUrl, request } from './http.js';
2
+ import { assertSecureUrl, ensureOk, joinUrl, rateLimitedError, request } from './http.js';
3
3
  export async function discoverApp(appUrl, version, fetchImpl) {
4
- assertSecureUrl(appUrl, 'Uygulama adresi');
4
+ assertSecureUrl(appUrl, 'App URL');
5
5
  const res = await request(joinUrl(appUrl, '/api/config'), { retries: 2, headers: { 'X-Enformik-CLI-Version': version }, fetchImpl });
6
6
  try {
7
7
  ensureOk(res);
8
8
  }
9
9
  catch (e) {
10
+ if (e instanceof ApiError && e.status === 429)
11
+ throw e;
10
12
  if (e instanceof ApiError)
11
- throw new CliError(`Uygulama yapılandırması okunamadı (HTTP ${e.status}). Adres doğru mu: ${appUrl}?`);
13
+ throw new CliError(`Could not read the app configuration (HTTP ${e.status}). Is the URL correct: ${appUrl}?`);
12
14
  throw e;
13
15
  }
14
16
  const j = (res.json ?? {});
15
17
  if (!j.oidc_authority)
16
- throw new CliError('Uygulama yapılandırması beklenen biçimde değil (oidc_authority yok).');
18
+ throw new CliError('The app configuration is not in the expected format (oidc_authority is missing).');
17
19
  if (!j.api_audience) {
18
- throw new CliError('Bu uygulama API istemcisi belirteçlerini kabul etmiyor (api_audience boş). Destek kaydı açın.');
20
+ throw new CliError('This app does not accept API client tokens (api_audience is empty). Open a support ticket.');
19
21
  }
20
22
  return { oidcAuthority: j.oidc_authority, apiAudience: j.api_audience, tenantId: j.tenant_id ?? '' };
21
23
  }
@@ -24,7 +26,7 @@ export function buildScope(audience) {
24
26
  }
25
27
  export async function fetchAccessToken(app, clientId, clientSecret, version, fetchImpl) {
26
28
  const url = joinUrl(app.oidcAuthority, '/oauth/v2/token');
27
- assertSecureUrl(url, 'Kimlik sağlayıcı adresi');
29
+ assertSecureUrl(url, 'Identity provider URL');
28
30
  const res = await request(url, {
29
31
  method: 'POST',
30
32
  headers: {
@@ -38,13 +40,15 @@ export async function fetchAccessToken(app, clientId, clientSecret, version, fet
38
40
  });
39
41
  if (!res.ok) {
40
42
  if (res.status === 400 || res.status === 401) {
41
- throw new CliError('Kimlik doğrulanamadı: istemci kimliği/sırrı geçersiz ya da istemci iptal edilmiş. Konsoldan sırrı yenileyin.');
43
+ throw new CliError('Authentication failed: the client ID/secret is invalid or the client has been revoked. Regenerate the secret in the console.');
42
44
  }
43
- throw new CliError(`Belirteç alınamadı (HTTP ${res.status}).`);
45
+ if (res.status === 429)
46
+ throw rateLimitedError(res);
47
+ throw new CliError(`Could not obtain a token (HTTP ${res.status}).`);
44
48
  }
45
49
  const j = res.json;
46
50
  if (!j?.access_token)
47
- throw new CliError('Kimlik sağlayıcı beklenen yanıtı vermedi (access_token yok).');
51
+ throw new CliError('The identity provider did not return the expected response (access_token is missing).');
48
52
  return { token: j.access_token, expiresInSec: j.expires_in ?? 0, claims: decodeJwtPayload(j.access_token) };
49
53
  }
50
54
  /** İmza DOĞRULANMAZ (yalnızca whoami'de bilgi göstermek için); yetkilendirme sunucuda yapılır. */
package/dist/cli.js CHANGED
@@ -25,45 +25,45 @@ const OPTIONS = {
25
25
  };
26
26
  const GLOBAL = ['json', 'help', 'version', 'debug', 'config'];
27
27
  export const COMMANDS = {
28
- link: { flags: ['console-url', 'app-url', 'workspace', 'module', 'manifest', 'dir', 'no-verify'], run: link, usage: 'enformik link [--console-url U] [--app-url U] [--workspace ID] [--module SLUG] [--manifest YOL] [--dir KLASÖR]' },
28
+ link: { flags: ['console-url', 'app-url', 'workspace', 'module', 'manifest', 'dir', 'no-verify'], run: link, usage: 'enformik link [--console-url U] [--app-url U] [--workspace ID] [--module SLUG] [--manifest PATH] [--dir DIR]' },
29
29
  whoami: { flags: ['console-url', 'app-url', 'workspace'], run: whoami, usage: 'enformik whoami' },
30
- 'ai init': { flags: ['lang', 'docs-url', 'offline'], run: aiInit, usage: 'enformik ai init [--lang tr|en] [--docs-url URL] [--offline]' },
31
- 'app check': { flags: ['console-url', 'app-url', 'workspace', 'module', 'manifest', 'entity', 'expect-host'], run: appCheck, usage: 'enformik app check [--entity a,b] [--manifest module.json]' },
30
+ 'ai init': { flags: ['lang', 'docs-url', 'offline'], run: aiInit, usage: 'enformik ai init [--lang en|tr] [--docs-url URL] [--offline]' },
31
+ 'app check': { flags: ['console-url', 'app-url', 'workspace', 'module', 'manifest', 'entity', 'expect-host'], run: appCheck, usage: 'enformik app check [--entity a,b] [--manifest module.json] [--expect-host <app-host>] (--expect-host checks the app host only)' },
32
32
  codegen: { flags: ['manifest', 'out', 'check'], run: codegen, usage: 'enformik codegen [--manifest module.json] [--out src/enformik.generated.ts] [--check]' },
33
33
  'schema diff': { flags: ['app-url', 'workspace', 'module', 'manifest', 'check', 'seed-data', 'expect-host'], run: schemaDiff, usage: 'enformik schema diff [--manifest module.json] [--check]' },
34
34
  'schema validate': { flags: ['app-url', 'workspace', 'module', 'manifest', 'seed-data', 'expect-host', 'profile', 'offline'], run: schemaValidate, usage: 'enformik schema validate [--manifest module.json] [--profile import|ai] [--offline]' },
35
35
  'schema install': { flags: ['app-url', 'workspace', 'module', 'manifest', 'yes', 'dry-run', 'seed-data', 'expect-host'], run: schemaInstall, usage: 'enformik schema install [--manifest module.json] [--yes] [--dry-run]' },
36
36
  'schema push': { flags: ['app-url', 'workspace', 'module', 'manifest', 'yes', 'seed-data', 'expect-host'], run: schemaPush, usage: 'enformik schema push [--manifest module.json] [--yes] [--seed-data]' },
37
- 'site deploy': { flags: ['console-url', 'dir', 'no-wait', 'timeout', 'expect-host'], run: siteDeploy, usage: 'enformik site deploy [--dir dist] [--no-wait] [--timeout 600] [--expect-host <site-adresi>]' },
38
- 'site status': { flags: ['console-url', 'expect-host'], run: siteStatus, usage: 'enformik site status [--expect-host <site-adresi>]' },
37
+ 'site deploy': { flags: ['console-url', 'dir', 'no-wait', 'timeout', 'expect-host'], run: siteDeploy, usage: 'enformik site deploy [--dir dist] [--no-wait] [--timeout 600] [--expect-host <site-host>]' },
38
+ 'site status': { flags: ['console-url', 'expect-host'], run: siteStatus, usage: 'enformik site status [--expect-host <site-host>]' },
39
39
  };
40
40
  export function helpText(version) {
41
- return `enformik ${version} — Enformik geliştirici CLI'si
41
+ return `enformik ${version} — the Enformik developer CLI
42
42
 
43
- Kullanım: enformik <komut> [bayraklar]
43
+ Usage: enformik <command> [flags]
44
44
 
45
- Komutlar
46
- link .enformik/config.json yazar (adresler; sır içermez)
47
- whoami Hangi kimlikle, hangi hedefe bağlanıldığını gösterir
48
- ai init Yapay zekâ ajanı rehberlerini sunucudan çeker (.enformik/ai/) ve AGENTS.md'ye talimat bloğu ekler
49
- app check Web uygulamasının entegrasyon sağlığını denetler (site girişi, modül, web kullanıcısı erişimi; çıkış 1 sorun, 2 uyarı)
50
- codegen module.json'dan TypeScript tipleri + tipli veri istemcisi üretir (--check: güncel değilse çıkış 2)
51
- schema validate Manifesti DB'ye dokunmadan doğrular (--profile ai: eksiksizlik; --offline: yalnızca yerel)
52
- schema diff Modül manifestini uygulamadakiyle karşılaştırır (--check: değişiklik varsa çıkış kodu 2)
53
- schema install Modülü uygulamaya İLK kez kurar (rol: Şema oluşturucu; --dry-run yalnızca gösterir)
54
- schema push Değişiklikleri uygular (etkileşimsiz ortamda --yes gerekir)
55
- site deploy Derlenmiş siteyi (varsayılan: dist) web sitesi ortamına yayınlar
56
- site status Hedef site ve canlı sürüm bilgisi
45
+ Commands
46
+ link Writes .enformik/config.json (URLs only; no secrets)
47
+ whoami Shows which identity connects to which target
48
+ ai init Downloads the AI agent guides from the server (.enformik/ai/) and adds an instruction block to AGENTS.md
49
+ app check Checks the web app's integration health (site sign-in, module, web user access; exit 1 = problem, 2 = warning)
50
+ codegen Generates TypeScript types + a typed data client from module.json (--check: exit 2 if out of date)
51
+ schema validate Validates the manifest without touching the DB (--profile ai: completeness; --offline: local only)
52
+ schema diff Compares the module manifest with the one in the app (--check: exit code 2 if there are changes)
53
+ schema install Installs the module into the app for the FIRST time (role: Schema builder; --dry-run only shows)
54
+ schema push Applies changes (--yes is required in non-interactive environments)
55
+ site deploy Publishes the built site (default: dist) to the website environment
56
+ site status Shows the target site and the live version
57
57
 
58
- Genel bayraklar: --json (makine-okunur çıktı), --config YOL, --debug, -h/--help, -v/--version
58
+ Global flags: --json (machine-readable output), --config PATH, --debug, -h/--help, -v/--version
59
59
 
60
- Sırlar yalnızca ortam değişkeniyle verilir:
61
- Şema yükseltme : ENFORMIK_CLIENT_ID, ENFORMIK_CLIENT_SECRET (+ ENFORMIK_APP_URL)
62
- Site dağıtımı : ENFORMIK_DEPLOY_KEY (+ ENFORMIK_CONSOLE_URL)
60
+ Secrets are provided only through environment variables:
61
+ Schema upgrade : ENFORMIK_CLIENT_ID, ENFORMIK_CLIENT_SECRET (+ ENFORMIK_APP_URL)
62
+ Site deployment: ENFORMIK_DEPLOY_KEY (+ ENFORMIK_CONSOLE_URL)
63
63
 
64
- Çıkış kodları: 0 başarı · 1 hata · 2 (--check) uygulanacak değişiklik var
64
+ Exit codes: 0 success · 1 error · 2 changes pending (--check) or warnings only (app check)
65
65
 
66
- Ayrıntılar: komut yardımı için "enformik <komut> --help"`;
66
+ Details: run "enformik <command> --help" for command help`;
67
67
  }
68
68
  function readVersion() {
69
69
  try {
@@ -82,7 +82,7 @@ export async function main(d) {
82
82
  catch (e) {
83
83
  // Ayrıştırma başarısız olduğu için --json değeri yok; ham argümandan anlaşılır (CAP-9: JSON kipinde stdout JSON).
84
84
  const out = createOutput({ json: d.argv.includes('--json'), stderr: d.stderr, stdout: d.stdout, env: d.env });
85
- out.error(`${e.message}\n"enformik --help" ile kullanımı görün.`);
85
+ out.error(`${e.message}\nRun "enformik --help" for usage.`);
86
86
  out.result({ error: 'usage', message: e.message });
87
87
  return 1;
88
88
  }
@@ -97,37 +97,41 @@ export async function main(d) {
97
97
  const find = (k) => (Object.hasOwn(COMMANDS, k) ? COMMANDS[k] : undefined);
98
98
  const cmd = find(name) ?? find(positionals[0] ?? '');
99
99
  const cmdName = find(name) ? name : positionals[0] ?? '';
100
- if (values.help || positionals.length === 0 || positionals[0] === 'help') {
100
+ // "enformik <typo> --help" is an error, not the general help; a command prefix ("schema --help") still shows the help.
101
+ const first = positionals[0];
102
+ const isPrefix = first !== undefined && Object.keys(COMMANDS).some((k) => k.startsWith(first + ' '));
103
+ const unknownWithHelp = values.help === true && first !== undefined && first !== 'help' && !cmd && (positionals.length >= 2 || !isPrefix);
104
+ if (!unknownWithHelp && (values.help || positionals.length === 0 || positionals[0] === 'help')) {
101
105
  if (cmd && values.help) {
102
- out.info(`Kullanım: ${cmd.usage}`);
106
+ out.info(`Usage: ${cmd.usage}`);
103
107
  return 0;
104
108
  }
105
109
  out.info(helpText(d.version));
106
110
  return positionals.length === 0 && !values.help ? 1 : 0;
107
111
  }
108
112
  if (!cmd) {
109
- out.error(`Bilinmeyen komut: ${positionals.join(' ')}\n"enformik --help" ile komutları görün.`);
110
- out.result({ error: 'unknown_command', message: `Bilinmeyen komut: ${positionals.join(' ')}` });
113
+ out.error(`Unknown command: ${positionals.join(' ')}\nRun "enformik --help" to see the commands.`);
114
+ out.result({ error: 'unknown_command', message: `Unknown command: ${positionals.join(' ')}` });
111
115
  return 1;
112
116
  }
113
117
  const used = Object.entries(values).filter(([, v]) => v !== undefined).map(([k]) => k);
114
118
  const bad = used.filter((k) => !GLOBAL.includes(k) && !cmd.flags.includes(k));
115
119
  if (bad.length > 0) {
116
- out.error(`"${cmdName}" komutunda geçersiz bayrak: ${bad.map((b) => '--' + b).join(', ')}\nKullanım: ${cmd.usage}`);
117
- out.result({ error: 'usage', message: `"${cmdName}" komutunda geçersiz bayrak: ${bad.map((b) => '--' + b).join(', ')}` });
120
+ out.error(`Invalid flag for "${cmdName}": ${bad.map((b) => '--' + b).join(', ')}\nUsage: ${cmd.usage}`);
121
+ out.result({ error: 'usage', message: `Invalid flag for "${cmdName}": ${bad.map((b) => '--' + b).join(', ')}` });
118
122
  return 1;
119
123
  }
120
124
  const extra = positionals.slice(cmdName.split(' ').length);
121
125
  if (extra.length > 0) {
122
- out.error(`Beklenmeyen argüman: ${extra.join(' ')}\nKullanım: ${cmd.usage}`);
123
- out.result({ error: 'usage', message: `Beklenmeyen argüman: ${extra.join(' ')}` });
126
+ out.error(`Unexpected argument: ${extra.join(' ')}\nUsage: ${cmd.usage}`);
127
+ out.result({ error: 'usage', message: `Unexpected argument: ${extra.join(' ')}` });
124
128
  return 1;
125
129
  }
126
130
  try {
127
131
  const { config } = loadConfig(d.cwd, typeof values.config === 'string' ? values.config : undefined);
128
132
  const flags = values;
129
133
  const settings = resolveSettings(config, d.env, flags);
130
- const prompts = realPrompts();
134
+ const prompts = realPrompts(d.stdin);
131
135
  const ctx = {
132
136
  out, settings, flags, cwd: d.cwd, env: d.env, version: d.version,
133
137
  isTTY: d.isTTY ?? !!process.stdin.isTTY,
@@ -144,12 +148,17 @@ export async function main(d) {
144
148
  out.result({ error: e.code ?? 'error', message: e.message });
145
149
  return e.exitCode;
146
150
  }
151
+ if (e instanceof ApiError && e.status === 429) {
152
+ out.error(e.message);
153
+ out.result({ error: e.code ?? 'rate_limited', message: e.message, status: 429, ...(e.retryAfterSeconds !== undefined ? { retryAfterSeconds: e.retryAfterSeconds } : {}) });
154
+ return 1;
155
+ }
147
156
  if (e instanceof ApiError) {
148
- out.error(`Sunucu hatası (HTTP ${e.status}): ${e.message}`);
157
+ out.error(`Server error (HTTP ${e.status}): ${e.message}`);
149
158
  out.result({ error: e.code ?? 'api_error', message: e.message, status: e.status });
150
159
  return 1;
151
160
  }
152
- out.error(`Beklenmeyen hata: ${e.message}`);
161
+ out.error(`Unexpected error: ${e.message}`);
153
162
  out.result({ error: 'unexpected', message: e.message });
154
163
  if (values.debug)
155
164
  out.error(e.stack ?? '');
@@ -165,5 +174,5 @@ catch {
165
174
  return p;
166
175
  } }
167
176
  if (invoked && invoked === fileURLToPath(import.meta.url)) {
168
- main({ argv: process.argv.slice(2), env: process.env, cwd: process.cwd(), version: readVersion() }).then((code) => { process.exitCode = code; }, (e) => { process.stderr.write(`Beklenmeyen hata: ${e.message}\n`); process.exitCode = 1; });
177
+ main({ argv: process.argv.slice(2), env: process.env, cwd: process.cwd(), version: readVersion() }).then((code) => { process.exitCode = code; }, (e) => { process.stderr.write(`Unexpected error: ${e.message}\n`); process.exitCode = 1; });
169
178
  }
package/dist/codegen.js CHANGED
@@ -56,15 +56,15 @@ export function generateClient(manifest) {
56
56
  const m = manifest.module ?? {};
57
57
  const entities = (manifest.entities ?? []).filter((e) => e && typeof e.slug === 'string');
58
58
  const out = [];
59
- out.push(`// Bu dosya \`enformik codegen\` ile ${m.slug ?? 'modül'}@${m.version ?? '?'} manifestinden ÜRETİLDİ — elle düzenlemeyin.`);
60
- out.push('// Yeniden üretmek için: npx @enformik/cli codegen (CI: codegen --check)');
59
+ out.push(`// This file was GENERATED by \`enformik codegen\` from the ${m.slug ?? 'module'}@${m.version ?? '?'} manifest — do not edit by hand.`);
60
+ out.push('// To regenerate: npx @enformik/cli codegen (CI: codegen --check)');
61
61
  out.push('');
62
- out.push('/** @enformik/auth istemcisinin bu dosyanın ihtiyaç duyduğu parçası. */');
62
+ out.push('/** The part of the @enformik/auth client that this file needs. */');
63
63
  out.push('export interface EnformikAuthLike { fetch(path: string, init?: RequestInit): Promise<Response> }');
64
64
  out.push('');
65
65
  out.push('export class EnformikApiError extends Error {');
66
66
  out.push(' constructor(public readonly status: number, public readonly body: unknown) {');
67
- out.push(" super(`Enformik API hatası (HTTP ${status})`);");
67
+ out.push(" super(`Enformik API error (HTTP ${status})`);");
68
68
  out.push(" this.name = 'EnformikApiError';");
69
69
  out.push(' }');
70
70
  out.push('}');
@@ -73,7 +73,7 @@ export function generateClient(manifest) {
73
73
  out.push('export interface ListOptions<R> {');
74
74
  out.push(' page?: number; limit?: number; search?: string;');
75
75
  out.push(" sort?: keyof R & string; sortDir?: 'ASC' | 'DESC';");
76
- out.push(' /** Alan adı → değer eşitlik/içerir filtresi. */');
76
+ out.push(' /** Field name -> value equality/contains filter. */');
77
77
  out.push(' filter?: Partial<Record<keyof R & string, string | number | boolean>>;');
78
78
  out.push('}');
79
79
  out.push('');
@@ -99,7 +99,7 @@ export function generateClient(manifest) {
99
99
  out.push(`export type ${T}Patch = Partial<${T}Input>;`);
100
100
  out.push('');
101
101
  }
102
- out.push('/** Tipli veri istemcisi: `const api = createClient(auth); await api.xyz.list({ limit: 20 })`. */');
102
+ out.push('/** Typed data client: `const api = createClient(auth); await api.xyz.list({ limit: 20 })`. */');
103
103
  out.push('export function createClient(auth: EnformikAuthLike) {');
104
104
  out.push(' async function call<T>(method: string, path: string, body?: unknown): Promise<{ data: T; res: Response }> {');
105
105
  out.push(" const res = await auth.fetch(path, { method, headers: body === undefined ? undefined : { 'Content-Type': 'application/json' }, body: body === undefined ? undefined : JSON.stringify(body) });");
@@ -2,7 +2,7 @@ import { createHash } from 'node:crypto';
2
2
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
3
3
  import { dirname, join, resolve } from 'node:path';
4
4
  import { CliError } from '../errors.js';
5
- import { assertSecureUrl, joinUrl, request } from '../http.js';
5
+ import { assertSecureUrl, joinUrl, rateLimitedError, request } from '../http.js';
6
6
  export const DEFAULT_DOCS_URL = 'https://help.enformik.com';
7
7
  const MAX_FILE_BYTES = 2 * 1024 * 1024;
8
8
  const SAFE_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]*(\/[A-Za-z0-9][A-Za-z0-9._-]*)*$/;
@@ -20,7 +20,7 @@ export function cmpVersion(a, b) {
20
20
  function parseIndex(raw) {
21
21
  const x = raw;
22
22
  if (!x || x.schemaVersion !== 1 || typeof x.guideVersion !== 'string' || !x.files || !x.shared || !Array.isArray(x.languages)) {
23
- throw new CliError('Rehber dizini (index.json) beklenen biçimde değil; CLI\'yi güncelleyin ya da daha sonra yeniden deneyin.');
23
+ throw new CliError('The guide index (index.json) is not in the expected format; update the CLI or try again later.');
24
24
  }
25
25
  return x;
26
26
  }
@@ -59,75 +59,79 @@ export function upsertBlock(existing, block) {
59
59
  if (s >= 0) {
60
60
  const e = existing.indexOf(END, s);
61
61
  if (e < 0)
62
- throw new CliError(`Talimat bloğunun başlangıç işaretçisi var ama bitiş işaretçisi (${END}) yok; dosya bozulmuş görünüyor. Dosyaya dokunulmadı: işaretçiyi onarın ya da bloğu elle silip yeniden çalıştırın.`);
62
+ throw new CliError(`The instruction block has a start marker but no end marker (${END}); the file looks corrupted. The file was not modified: repair the marker, or delete the block by hand and run again.`);
63
63
  return existing.slice(0, s) + block + existing.slice(e + END.length).replace(/^\n/, '');
64
64
  }
65
65
  return existing.replace(/\n*$/, '\n\n') + block;
66
66
  }
67
67
  async function download(base, rel, sha, ctx) {
68
68
  if (!SAFE_NAME.test(rel) || rel.includes('..'))
69
- throw new CliError(`Rehber dizininde güvensiz dosya adı: ${rel}`);
69
+ throw new CliError(`Unsafe file name in the guide index: ${rel}`);
70
70
  const res = await request(joinUrl(base, rel), { retries: 2, fetchImpl: ctx.fetchImpl, headers: { 'X-Enformik-CLI-Version': ctx.version } });
71
+ if (res.status === 429)
72
+ throw rateLimitedError(res);
71
73
  if (!res.ok)
72
- throw new CliError(`Rehber dosyası indirilemedi: ${rel} (HTTP ${res.status}).`);
74
+ throw new CliError(`Could not download guide file: ${rel} (HTTP ${res.status}).`);
73
75
  if (Buffer.byteLength(res.text) > MAX_FILE_BYTES)
74
- throw new CliError(`Rehber dosyası beklenenden büyük: ${rel}.`);
76
+ throw new CliError(`Guide file is larger than expected: ${rel}.`);
75
77
  if (createHash('sha256').update(res.text).digest('hex') !== sha) {
76
- throw new CliError(`Rehber dosyasının sağlama toplamı uyuşmuyor: ${rel} (indirme bozulmuş ya da yayın yarım kalmış olabilir; yeniden deneyin).`);
78
+ throw new CliError(`Checksum mismatch for guide file: ${rel} (the download may be corrupted or the release incomplete; try again).`);
77
79
  }
78
80
  return res.text;
79
81
  }
80
82
  /**
81
- * enformik ai init [--lang tr|en] [--docs-url URL] [--offline]
82
- * Yapay zekâ ajanı rehberlerini Enformik dokümantasyon sitesinden (sunucudan) çeker, sağlama toplamlarını doğrular,
83
- * `.enformik/ai/` altına yazar ve AGENTS.md'ye (varsa CLAUDE.md'ye de) kısa bir talimat bloğu ekler. Sır yazmaz.
83
+ * enformik ai init [--lang en|tr] [--docs-url URL] [--offline]
84
+ * Downloads the AI agent guides from the Enformik documentation site, verifies checksums,
85
+ * writes them under `.enformik/ai/` and adds a short instruction block to AGENTS.md (and CLAUDE.md if present). Writes no secrets.
84
86
  */
85
87
  export async function aiInit(ctx) {
86
88
  const { out } = ctx;
87
- const lang = ctx.flags.lang === undefined ? 'tr' : ctx.flags.lang;
89
+ const lang = ctx.flags.lang === undefined ? 'en' : ctx.flags.lang;
88
90
  if (lang !== 'tr' && lang !== 'en')
89
- throw new CliError(`--lang "tr" ya da "en" olmalı (verilen: ${String(lang)}).`);
91
+ throw new CliError(`--lang must be "en" or "tr" (got: ${String(lang)}).`);
90
92
  const root = resolve(ctx.cwd, '.enformik', 'ai');
91
93
  let index;
92
94
  let source = 'server';
93
95
  if (ctx.flags.offline === true) {
94
96
  const cached = join(root, 'index.json');
95
97
  if (!existsSync(cached))
96
- throw new CliError('Önbellekte rehber yok. Önce çevrimiçi `enformik ai init` çalıştırın.');
98
+ throw new CliError('No guide in the cache. Run `enformik ai init` online first.');
97
99
  let raw;
98
100
  try {
99
101
  raw = JSON.parse(readFileSync(cached, 'utf8'));
100
102
  }
101
103
  catch {
102
- throw new CliError('Önbellekteki rehber dizini (index.json) okunamadı; çevrimiçi `enformik ai init` ile yeniden indirin.');
104
+ throw new CliError('Could not read the cached guide index (index.json); download it again with `enformik ai init` online.');
103
105
  }
104
106
  index = parseIndex(raw);
105
107
  if (cmpVersion(ctx.version, index.minCliVersion) < 0) {
106
- throw new CliError(`Önbellekteki rehber en az CLI ${index.minCliVersion} ister (sizde ${ctx.version}). \`npm install -g @enformik/cli\` ile güncelleyin.`);
108
+ throw new CliError(`The cached guide requires CLI ${index.minCliVersion} or newer (you have ${ctx.version}). Update with \`npm install -g @enformik/cli\`.`);
107
109
  }
108
110
  source = 'cache';
109
- out.warn(`Önbellekteki rehber kullanılıyor (sürüm ${index.guideVersion}); güncel olmayabilir.`);
111
+ out.warn(`Using the cached guide (version ${index.guideVersion}); it may be out of date.`);
110
112
  if (!existsSync(join(root, lang, 'overview.md')))
111
- throw new CliError(`Önbellekte "${lang}" dilinde rehber yok.`);
113
+ throw new CliError(`No guide in language "${lang}" in the cache.`);
112
114
  }
113
115
  else {
114
116
  const base = joinUrl(ctx.settings.docsUrl, '/ai/');
115
- assertSecureUrl(base, 'Doküman adresi');
117
+ assertSecureUrl(base, 'Docs URL');
116
118
  let res;
117
119
  try {
118
120
  res = await request(joinUrl(base, 'index.json'), { retries: 2, fetchImpl: ctx.fetchImpl, headers: { 'X-Enformik-CLI-Version': ctx.version } });
119
121
  }
120
122
  catch (e) {
121
- throw new CliError(`${e.message}\nRehberler sunucudan çekilir; çevrimdışıysanız ve daha önce indirdiyseniz \`enformik ai init --offline\` kullanın.`);
123
+ throw new CliError(`${e.message}\nGuides are downloaded from the server; if you are offline and have downloaded them before, use \`enformik ai init --offline\`.`);
122
124
  }
125
+ if (res.status === 429)
126
+ throw rateLimitedError(res);
123
127
  if (!res.ok)
124
- throw new CliError(`Rehber dizini alınamadı (HTTP ${res.status}): ${joinUrl(base, 'index.json')}`);
128
+ throw new CliError(`Could not fetch the guide index (HTTP ${res.status}): ${joinUrl(base, 'index.json')}`);
125
129
  index = parseIndex(res.json);
126
130
  if (cmpVersion(ctx.version, index.minCliVersion) < 0) {
127
- throw new CliError(`Bu rehber en az CLI ${index.minCliVersion} ister (sizde ${ctx.version}). \`npm install -g @enformik/cli\` ile güncelleyin.`);
131
+ throw new CliError(`This guide requires CLI ${index.minCliVersion} or newer (you have ${ctx.version}). Update with \`npm install -g @enformik/cli\`.`);
128
132
  }
129
133
  if (!index.languages.includes(lang) || !index.files[lang])
130
- throw new CliError(`Rehber "${lang}" dilinde yok (var olanlar: ${index.languages.join(', ')}).`);
134
+ throw new CliError(`The guide is not available in "${lang}" (available: ${index.languages.join(', ')}).`);
131
135
  // Önce hepsini indirip doğrula, sonra yaz: yarım rehber bırakma.
132
136
  const contents = new Map();
133
137
  for (const [name, sha] of Object.entries(index.files[lang]))
@@ -160,9 +164,9 @@ export async function aiInit(ctx) {
160
164
  written.push(u.f);
161
165
  }
162
166
  const fileCount = Object.keys(index.files[lang] ?? {}).length + Object.keys(index.shared).length;
163
- out.info(`✓ Ajan rehberi hazır (sürüm ${index.guideVersion}, ${lang}, ${fileCount} dosya, kaynak: ${source === 'server' ? 'sunucu' : 'önbellek'}) → .enformik/ai/`);
164
- out.info(written.length > 0 ? ` Talimat bloğu güncellendi: ${written.join(', ')}` : ' Talimat bloğu zaten güncel.');
165
- out.info(' Sonraki adım: ajanın `.enformik/ai/' + lang + '/overview.md` dosyasını okuması.');
167
+ out.info(`✓ Agent guide ready (version ${index.guideVersion}, ${lang}, ${fileCount} files, source: ${source === 'server' ? 'server' : 'cache'}) → .enformik/ai/`);
168
+ out.info(written.length > 0 ? ` Instruction block updated: ${written.join(', ')}` : ' Instruction block already up to date.');
169
+ out.info(' Next step: have the agent read `.enformik/ai/' + lang + '/overview.md`.');
166
170
  out.result({ guideVersion: index.guideVersion, lang, source, files: fileCount, agentFiles: written, dir: '.enformik/ai' });
167
171
  return 0;
168
172
  }