@venizia/ardor 0.4.8-5 → 0.4.8-6

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,6 +1,6 @@
1
1
  /** `ardor codemod namespace`: a dry run unless `--write`; `--check` is the dry run that fails on work left. */
2
2
  export declare class NamespaceCommand {
3
- static readonly USAGE = "Usage: ardor codemod namespace [paths...] [--hooks] [--write [--allow-dirty] | --check]\n\nMoves the names removed from @venizia/ardor to what replaces them:\n\n isDefined, isString, isNumber, isObject, isValidDate ValueUtility.<name>\n isBrowser BrowserUtility.isBrowser\n toDataUrl FileUtility.toDataUrl\n getRuntimeConfig RuntimeConfig.get\n createAppDispatch, createAppSelectors DispatchHooks.create, SelectorHooks.create\n resolveApplicationProviders ApplicationProviders.resolve\n hasPendingFeatureMessages, loadFeatureMessages Features.hasPendingMessages, Features.loadMessages\n NumberIdType, StringIdType, NullableType number, string, undefined | null | void\n BaseHelper, LogLevels, ILogger, TLogLevel the same names from @venizia/ignis-helpers/core\n Logger.getInstance({ scope }), new Logger({ scope }) LoggerFactory.getLogger([scope]) (enableDebug is dropped, with a note)\n Logger (a type) ILogger\n ISendResponse INetworkResponse\n\nRemoved with nothing to write in their place, so only listed with their line: isEditableTarget,\nTDataCount, ISyncFC, IListFilter.offset (use skip; listed wherever a file that imports IListFilter or ListFilterBuilder reads or sets .offset), useSizer (use useElementSize), useInjectableContainer (renamed useApplicationContainer), App.DEFAULT_LOCALE, HeaderConstants.REQUEST_DEVICE_INFO, HeaderConstants.RESPONSE_FORMAT,\nthe enableDebug prop of ArdorApplication (pass logger instead), and a Logger use with no one-line form.\n\nIt rewrites a name imported from @venizia/ardor, @venizia/ardor-kernel, @venizia/ardor-react or\n@venizia/ardor-admin, its uses, and the import. It reads syntax only: a name the file also declares\nitself, a re-export by name and a class name already taken are listed with their line and left for you.\n\n --hooks also move a hook imported from @venizia/ardor to the Ardor namespace (useRepository()\n becomes Ardor.useRepository()). A hook imported from @venizia/ardor-react or\n @venizia/ardor-admin is never touched: those packages cannot import Ardor.\n --write write the changed files (without it, nothing is written)\n --allow-dirty with --write, also write a file that has local changes, such as one another codemod\n just changed (devz codemod namespace-functions --write leaves the tree dirty)\n --check dry run that exits 1 when anything would change or is left (for CI)\n --help print this text and exit\n\nWith no flag it lists what would change and writes nothing. Inside a git repository it reads the\nfiles git knows (tracked, or untracked and not ignored), and --write refuses a file with local\nchanges (changed, staged or untracked), so git can undo the codemod: the file is listed as \"refused\"\nwith its path, the summary counts it and the run exits 1. Commit or stash it and run again, or run\nagain with --allow-dirty to write it anyway (it keeps the other codemod's edits). To run two codemods\none after the other in a dirty tree, give this one --write --allow-dirty.\nRun your formatter afterwards: the new names are appended to the import they join.";
3
+ static readonly USAGE = "Usage: ardor codemod namespace [paths...] [--hooks] [--write [--allow-dirty] | --check]\n\nMoves the names removed from @venizia/ardor to what replaces them:\n\n isDefined, isString, isNumber, isObject, isValidDate ValueUtility.<name>\n isBrowser BrowserUtility.isBrowser\n toDataUrl FileUtility.toDataUrl\n getRuntimeConfig RuntimeConfig.get\n createAppDispatch, createAppSelectors DispatchHooks.create, SelectorHooks.create\n resolveApplicationProviders ApplicationProviders.resolve\n hasPendingFeatureMessages, loadFeatureMessages Features.hasPendingMessages, Features.loadMessages\n NumberIdType, StringIdType, NullableType number, string, undefined | null | void\n BaseHelper, LogLevels, ILogger, TLogLevel the same names from @venizia/ignis-helpers/core\n Logger.getInstance({ scope }), new Logger({ scope }) LoggerFactory.getLogger([scope]) (enableDebug is dropped, with a note)\n Logger (a type) ILogger\n ISendResponse INetworkResponse\n\nRemoved with nothing to write in their place, so only listed with their line: isEditableTarget,\nTDataCount, ISyncFC, IListFilter.offset (use skip; listed wherever a file that imports IListFilter or ListFilterBuilder reads or sets .offset), useSizer (use useElementSize), useInjectableContainer (renamed useApplicationContainer), App.DEFAULT_LOCALE, HeaderConstants.REQUEST_DEVICE_INFO, HeaderConstants.RESPONSE_FORMAT,\nthe enableDebug prop of ArdorApplication (pass logger instead), and a Logger use with no one-line form.\n\nIt rewrites a name imported from @venizia/ardor, @venizia/ardor-kernel, @venizia/ardor-react or\n@venizia/ardor-admin, its uses, and the import. It reads syntax only: a name the file also declares\nitself, a re-export by name and a class name already taken are listed with their line and left for you.\n\n --hooks also move a hook imported from @venizia/ardor to the Ardor namespace (useRepository()\n becomes Ardor.useRepository()). A hook imported from @venizia/ardor-react or\n @venizia/ardor-admin is never touched: those packages cannot import Ardor.\n --write write the changed files (without it, nothing is written)\n --allow-dirty with --write, also write a file that has local changes, such as one another codemod\n just changed (devz codemod namespace-functions --write leaves the tree dirty)\n --check dry run that exits 1 when anything would change or is left (for CI)\n --help print this text and exit\n\n--write is all or nothing: it reads and rewrites every file in memory first, and writes only if every\nfile went through. A file that cannot be parsed or rewritten is listed as \"failed\" with its path, the\nrun exits 1 and nothing is written. A file that is not valid UTF-8 and would change is a failure too:\nit is left alone rather than damaged. Outside a git repository nothing can undo a write, so --write\nrefuses unless you give --allow-dirty.\n\nWith no flag it lists what would change and writes nothing. Inside a git repository it reads the\nfiles git knows (tracked, or untracked and not ignored), and --write refuses a file with local\nchanges (changed, staged or untracked), so git can undo the codemod: the file is listed as \"refused\"\nwith its path, the summary counts it and the run exits 1. Commit or stash it and run again, or run\nagain with --allow-dirty to write it anyway (it keeps the other codemod's edits). To run two codemods\none after the other in a dirty tree, give this one --write --allow-dirty.\nRun your formatter afterwards: the new names are appended to the import they join.";
4
4
  /** Runs the command and returns the exit code; prints to the console. */
5
5
  static run(opts: {
6
6
  root: string;
@@ -8,6 +8,15 @@ export declare class NamespaceCommand {
8
8
  }): Promise<number>;
9
9
  private static loadTypeScript;
10
10
  private static sweep;
11
+ /**
12
+ * Writes every file next to itself first, and renames them into place only when all were written,
13
+ * so a failure leaves no file changed. A file that is not the file that was read (a link swapped in,
14
+ * an edit made meanwhile) is refused: the rename replaces a link instead of following it, and a
15
+ * hard link is not written through.
16
+ */
17
+ private static writeAll;
18
+ /** Text for the terminal: control, format and line-separator characters, such as an escape sequence in a file name, become `?`. */
19
+ private static clean;
11
20
  private static report;
12
21
  }
13
22
  //# sourceMappingURL=cli.d.ts.map
@@ -1,4 +1,4 @@
1
- import { readFileSync, writeFileSync } from 'node:fs';
1
+ import { chmodSync, existsSync, lstatSync, readFileSync, renameSync, rmSync, writeFileSync, } from 'node:fs';
2
2
  import { relative, resolve } from 'node:path';
3
3
  import { CodemodFiles } from './files.js';
4
4
  import { Migration } from './migrate.js';
@@ -36,6 +36,12 @@ itself, a re-export by name and a class name already taken are listed with their
36
36
  --check dry run that exits 1 when anything would change or is left (for CI)
37
37
  --help print this text and exit
38
38
 
39
+ --write is all or nothing: it reads and rewrites every file in memory first, and writes only if every
40
+ file went through. A file that cannot be parsed or rewritten is listed as "failed" with its path, the
41
+ run exits 1 and nothing is written. A file that is not valid UTF-8 and would change is a failure too:
42
+ it is left alone rather than damaged. Outside a git repository nothing can undo a write, so --write
43
+ refuses unless you give --allow-dirty.
44
+
39
45
  With no flag it lists what would change and writes nothing. Inside a git repository it reads the
40
46
  files git knows (tracked, or untracked and not ignored), and --write refuses a file with local
41
47
  changes (changed, staged or untracked), so git can undo the codemod: the file is listed as "refused"
@@ -98,6 +104,11 @@ export class NamespaceCommand {
98
104
  const paths = args.filter((argument) => !argument.startsWith('-'));
99
105
  const targets = (paths.length > 0 ? paths : ['.']).map((path) => resolve(root, path));
100
106
  const hooks = args.includes(CodemodFlags.HOOKS);
107
+ const missing = targets.filter((target) => !existsSync(target));
108
+ if (missing.length > 0) {
109
+ console.error(`ardor codemod: no such file or folder: ${missing.map(NamespaceCommand.clean).join(', ')}`);
110
+ return USAGE_ERROR_CODE;
111
+ }
101
112
  return NamespaceCommand.sweep({ root, ts, targets, hooks, isWrite, isCheck, isAllowDirty });
102
113
  }
103
114
  static async loadTypeScript() {
@@ -114,43 +125,118 @@ export class NamespaceCommand {
114
125
  static sweep(opts) {
115
126
  const { root, ts, targets, hooks, isWrite, isCheck, isAllowDirty } = opts;
116
127
  const files = [...new Set(targets.flatMap((target) => CodemodFiles.collect({ target })))];
117
- const dirty = isWrite && !isAllowDirty
118
- ? new Set(targets.flatMap((target) => [...(CodemodFiles.dirty({ target }) ?? [])]))
119
- : new Set();
128
+ const dirtyOf = targets.map((target) => CodemodFiles.dirty({ target }));
129
+ if (isWrite && !isAllowDirty && dirtyOf.some((dirty) => dirty === undefined)) {
130
+ console.error(`ardor codemod: this is not a git repository, so nothing can undo ${CodemodFlags.WRITE}; run again with ${CodemodFlags.ALLOW_DIRTY} to write anyway`);
131
+ return 1;
132
+ }
133
+ const dirty = new Set(isAllowDirty ? [] : dirtyOf.flatMap((paths) => [...(paths ?? [])]));
120
134
  const changes = [];
135
+ const pending = [];
121
136
  const refused = [];
137
+ const failed = [];
122
138
  const left = [];
123
139
  const notes = [];
124
140
  for (const path of files) {
125
- const text = readFileSync(path, 'utf8');
126
- const result = Migration.run({ ts, fileName: path, text, hooks });
127
- for (const finding of result.findings) {
128
- (finding.kind === 'note' ? notes : left).push({ path, finding });
141
+ try {
142
+ const stat = lstatSync(path);
143
+ const bytes = readFileSync(path);
144
+ const text = bytes.toString('utf8');
145
+ const result = Migration.run({ ts, fileName: path, text, hooks });
146
+ for (const finding of result.findings) {
147
+ (finding.kind === 'note' ? notes : left).push({ path, finding });
148
+ }
149
+ if (result.text === text) {
150
+ continue;
151
+ }
152
+ if (!Buffer.from(text, 'utf8').equals(bytes)) {
153
+ failed.push({ path, reason: 'it is not valid UTF-8, so a rewrite would damage it' });
154
+ continue;
155
+ }
156
+ if (isWrite && dirty.has(path)) {
157
+ refused.push(path);
158
+ continue;
159
+ }
160
+ changes.push({ path, renamed: result.renamed });
161
+ pending.push({ path, text: result.text, stat });
129
162
  }
130
- if (result.text === text) {
131
- continue;
163
+ catch (error) {
164
+ failed.push({ path, reason: error instanceof Error ? error.message : String(error) });
132
165
  }
133
- if (isWrite && dirty.has(path)) {
134
- refused.push(path);
135
- continue;
166
+ }
167
+ if (isWrite && failed.length === 0) {
168
+ failed.push(...NamespaceCommand.writeAll({ pending }));
169
+ }
170
+ return NamespaceCommand.report({
171
+ root,
172
+ isWrite,
173
+ isCheck,
174
+ changes,
175
+ refused,
176
+ failed,
177
+ left,
178
+ notes,
179
+ });
180
+ }
181
+ /**
182
+ * Writes every file next to itself first, and renames them into place only when all were written,
183
+ * so a failure leaves no file changed. A file that is not the file that was read (a link swapped in,
184
+ * an edit made meanwhile) is refused: the rename replaces a link instead of following it, and a
185
+ * hard link is not written through.
186
+ */
187
+ static writeAll(opts) {
188
+ const staged = [];
189
+ const failures = [];
190
+ for (const { path, text, stat } of opts.pending) {
191
+ const temp = `${path}.${process.pid}.codemod`;
192
+ try {
193
+ const now = lstatSync(path);
194
+ if (!now.isFile() || now.mtimeMs !== stat.mtimeMs || now.size !== stat.size) {
195
+ failures.push({
196
+ path,
197
+ reason: 'it changed while the codemod ran, so it was not written',
198
+ });
199
+ continue;
200
+ }
201
+ writeFileSync(temp, text);
202
+ chmodSync(temp, now.mode & 0o7777);
203
+ staged.push({ path, temp });
204
+ }
205
+ catch (error) {
206
+ rmSync(temp, { force: true });
207
+ failures.push({ path, reason: error instanceof Error ? error.message : String(error) });
136
208
  }
137
- changes.push({ path, renamed: result.renamed });
138
- if (isWrite) {
139
- writeFileSync(path, result.text);
209
+ }
210
+ if (failures.length > 0) {
211
+ for (const { temp } of staged) {
212
+ rmSync(temp, { force: true });
140
213
  }
214
+ return failures;
141
215
  }
142
- return NamespaceCommand.report({ root, isWrite, isCheck, changes, refused, left, notes });
216
+ for (const { path, temp } of staged) {
217
+ renameSync(temp, path);
218
+ }
219
+ return [];
220
+ }
221
+ /** Text for the terminal: control, format and line-separator characters, such as an escape sequence in a file name, become `?`. */
222
+ static clean(text) {
223
+ return text.replace(/[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/gu, '?');
143
224
  }
144
225
  static report(opts) {
145
- const { root, isWrite, isCheck, changes, refused, left, notes } = opts;
146
- const verb = isWrite ? 'changed' : 'would change';
147
- const show = (path) => relative(root, path) || path;
226
+ const { root, isWrite, isCheck, changes, refused, failed, left, notes } = opts;
227
+ // Nothing is written when a file failed, so the files are not "changed".
228
+ const verb = isWrite && failed.length === 0 ? 'changed' : 'would change';
229
+ // A path comes from the file system: a control character in it is shown as `?`, never sent to the terminal.
230
+ const show = (path) => NamespaceCommand.clean(relative(root, path) || path);
148
231
  for (const change of changes) {
149
232
  console.log(`${verb} ${show(change.path)} (${change.renamed} use${change.renamed === 1 ? '' : 's'})`);
150
233
  }
151
234
  for (const path of refused) {
152
235
  console.error(`refused ${show(path)}: it has local changes, so git cannot undo the codemod there; commit or stash it and run again, or run again with ${CodemodFlags.ALLOW_DIRTY}`);
153
236
  }
237
+ for (const { path, reason } of failed) {
238
+ console.error(NamespaceCommand.clean(`failed ${show(path)}: ${reason}`));
239
+ }
154
240
  // A note is a change made on purpose that is worth knowing; it is not work left, so --check ignores it.
155
241
  for (const { path, finding } of notes) {
156
242
  console.log(`note ${show(path)}:${finding.line} ${finding.name}: ${finding.reason}`);
@@ -158,9 +244,14 @@ export class NamespaceCommand {
158
244
  for (const { path, finding } of left) {
159
245
  console.error(`left ${show(path)}:${finding.line} ${finding.name}: ${finding.reason}`);
160
246
  }
161
- const refusedNote = refused.length > 0 ? `, ${refused.length} refused (listed above)` : '';
247
+ const refusedNote = [
248
+ refused.length > 0 ? `, ${refused.length} refused (listed above)` : '',
249
+ failed.length > 0
250
+ ? `, ${failed.length} failed (listed above)${isWrite ? ', nothing was written' : ''}`
251
+ : '',
252
+ ].join('');
162
253
  console.log(`${changes.length} file${changes.length === 1 ? '' : 's'} ${verb}${refusedNote}, ${left.length} left for you${isWrite || changes.length === 0 ? '' : '. Run again with --write to apply.'}`);
163
- if (refused.length > 0) {
254
+ if (refused.length > 0 || failed.length > 0) {
164
255
  return 1;
165
256
  }
166
257
  return isCheck && (changes.length > 0 || left.length > 0) ? 1 : 0;
@@ -7,7 +7,7 @@ export declare class CodemodFiles {
7
7
  static isSource(opts: {
8
8
  name: string;
9
9
  }): boolean;
10
- /** Every source file under `target`; a file target is returned as it is. */
10
+ /** Every source file under `target`; a file target is returned as it is. A symlink or a file that is not a source file is never returned. */
11
11
  static collect(opts: {
12
12
  target: string;
13
13
  }): string[];
@@ -1,5 +1,5 @@
1
1
  import { execFileSync } from 'node:child_process';
2
- import { existsSync, readdirSync, statSync } from 'node:fs';
2
+ import { lstatSync, readdirSync, statSync } from 'node:fs';
3
3
  import { dirname, join } from 'node:path';
4
4
  const SKIPPED_DIRECTORIES = new Set(['node_modules', 'dist', 'build', '.git']);
5
5
  const SOURCE_PATTERN = /\.(?:ts|tsx|mts|cts)$/;
@@ -13,11 +13,15 @@ export class CodemodFiles {
13
13
  static isSource(opts) {
14
14
  return SOURCE_PATTERN.test(opts.name) && !DECLARATION_PATTERN.test(opts.name);
15
15
  }
16
- /** Every source file under `target`; a file target is returned as it is. */
16
+ /** Every source file under `target`; a file target is returned as it is. A symlink or a file that is not a source file is never returned. */
17
17
  static collect(opts) {
18
18
  const { target } = opts;
19
- if (statSync(target).isFile()) {
20
- return [target];
19
+ // Read without following a link: a symlink is never a file to write, whatever it points to.
20
+ if (lstatSync(target).isFile()) {
21
+ return CodemodFiles.isSource({ name: target }) ? [target] : [];
22
+ }
23
+ if (!statSync(target).isDirectory()) {
24
+ return [];
21
25
  }
22
26
  return CodemodFiles.fromGit({ target }) ?? CodemodFiles.fromWalk({ target });
23
27
  }
@@ -42,7 +46,7 @@ export class CodemodFiles {
42
46
  .filter((path) => path !== '' && CodemodFiles.isSource({ name: path }))
43
47
  .filter((path) => !path.split('/').some((segment) => SKIPPED_DIRECTORIES.has(segment)))
44
48
  .map((path) => join(target, path))
45
- .filter((path) => existsSync(path) && statSync(path).isFile());
49
+ .filter((path) => lstatSync(path, { throwIfNoEntry: false })?.isFile() === true);
46
50
  }
47
51
  static fromWalk(opts) {
48
52
  const files = [];
@@ -17,6 +17,11 @@
17
17
  FROM docker.io/oven/bun:1.4.3-alpine AS deps
18
18
 
19
19
  # A private registry, when the build runs where the public one is not reachable. Empty by default.
20
+ # The URL stays in the metadata of this stage and the next two (not in the final image): never put a user or a
21
+ # password in it. For a registry that needs credentials, mount a bunfig.toml for the install only, with BuildKit:
22
+ # RUN --mount=type=secret,id=bunfig,target=/home/bun/.bunfig.toml,uid=1000 bun install ...
23
+ # docker build --secret id=bunfig,src=./bunfig.toml ...
24
+ # A project that wants fixed base images pins them by digest (FROM image@sha256:...).
20
25
  ARG BUN_CONFIG_REGISTRY=
21
26
  ENV BUN_CONFIG_REGISTRY=${BUN_CONFIG_REGISTRY}
22
27
 
@@ -10,8 +10,9 @@
10
10
  # What is published: the variables named VITE_ followed by capitals, digits and underscores, and nothing
11
11
  # else. That is Vite's own rule for what reaches the browser, so the same names work in dev and here.
12
12
  # Every published value is readable by every visitor: never put a secret in a VITE_ variable. A name
13
- # with SECRET, PASSWORD or TOKEN in it stops the container, to catch that mistake early (a name with KEY
14
- # is fine: public keys are normal). Values are escaped as JavaScript string content, so no value can
13
+ # naming a secret (SECRET, PASSWORD, PASSWD, PASSPHRASE, PASSCODE, PASS as a word, PRIVATE, CREDENTIAL or TOKEN),
14
+ # or a value that is a URL carrying a user and a password, stops the container, to catch that mistake early
15
+ # (a name with KEY is fine, public keys are normal; this is a best-effort tripwire, not a secret scanner). Values are escaped as JavaScript string content, so no value can
15
16
  # end its string or the file.
16
17
  #
17
18
  # Required variables: APP_REQUIRED_ENV lists, comma separated, the variables the app cannot run without,
@@ -89,7 +90,8 @@ BEGIN {
89
90
  if (name !~ /^VITE_[A-Z0-9_]+$/) {
90
91
  continue
91
92
  }
92
- if (name ~ /SECRET|PASSWORD|TOKEN/) {
93
+ # Names that say secret, and a URL that carries a user and a password; a value is never printed.
94
+ if (name ~ /SECRET|PASSWORD|PASSWD|PASSPHRASE|PASSCODE|(^|_)PASS(_|$)|PRIVATE|CREDENTIAL|TOKEN/ || ENVIRON[name] ~ /(^|:)\/\/[^\/@?# ]*:[^\/@?# ]+@/) {
93
95
  print "[runtime-config] " name " looks like a secret, and every VITE_ variable is published to every visitor: rename it or keep it out of VITE_" > "/dev/stderr"
94
96
  failed = 1
95
97
  continue
@@ -10,6 +10,8 @@
10
10
  # The upstream is written into the nginx configuration, so it must be an address and nothing else: a value with a
11
11
  # character outside letters, digits, brackets (an IPv6 address) and `._:/@%+-` stops the container, naming the variable and never the value.
12
12
  #
13
+ # NGINX_LOCAL_RESOLVERS is checked the same way: addresses and spaces.
14
+ #
13
15
  # API_PROXY_SOURCE and API_PROXY_TARGET name other files, for the tests; they are not VITE_ names, so never published.
14
16
  set -eu
15
17
 
@@ -31,6 +33,23 @@ case "$API_UPSTREAM" in
31
33
  ;;
32
34
  esac
33
35
 
36
+ # The resolvers are written into the nginx configuration too: addresses (IPv4, IPv6 in brackets) and
37
+ # spaces between them, nothing else.
38
+ case "${NGINX_LOCAL_RESOLVERS:-}" in
39
+ *[!\ ]*) ;;
40
+ *)
41
+ echo "[api-proxy] FATAL: NGINX_LOCAL_RESOLVERS is empty, and nginx needs at least one resolver for /api" >&2
42
+ exit 1
43
+ ;;
44
+ esac
45
+
46
+ case "${NGINX_LOCAL_RESOLVERS:-}" in
47
+ *[!][0-9A-Fa-f.:\ ]*)
48
+ echo "[api-proxy] FATAL: NGINX_LOCAL_RESOLVERS has a character that is not part of an address" >&2
49
+ exit 1
50
+ ;;
51
+ esac
52
+
34
53
  # Only these two: nginx's own variables ($api_upstream, $host) stay as they are.
35
54
  envsubst '${API_UPSTREAM} ${NGINX_LOCAL_RESOLVERS}' < "$SOURCE" > "$TARGET"
36
55
  echo "[api-proxy] /api goes to the upstream of API_UPSTREAM"
@@ -9,6 +9,10 @@ location /api/ {
9
9
  proxy_pass $api_upstream;
10
10
  proxy_http_version 1.1;
11
11
  proxy_set_header Host $host;
12
+ # Behind a trusted front proxy (an ingress, a load balancer) this keeps the client address it appends. A
13
+ # browser can put its own value first in X-Forwarded-For, and $scheme is http here whatever the front proxy
14
+ # served: a backend must trust only the entries its own proxy added, and learn the scheme from that proxy. With
15
+ # no front proxy, replace the value with $remote_addr.
12
16
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
13
17
  proxy_set_header X-Forwarded-Proto $scheme;
14
18
  }
@@ -4,8 +4,10 @@ add_header X-Content-Type-Options "nosniff" always;
4
4
  add_header Referrer-Policy "strict-origin-when-cross-origin" always;
5
5
  add_header X-Frame-Options "SAMEORIGIN" always;
6
6
 
7
- # A Content-Security-Policy depends on what the app loads, so none is sent. An example to adapt: start with
8
- # Content-Security-Policy-Report-Only, watch the browser console, then switch to the enforcing header. Admin
9
- # libraries often need 'unsafe-inline' for styles. `frame-ancestors` replaces X-Frame-Options where both exist.
10
- #
11
- # add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'self'" always;
7
+ # A Content-Security-Policy, in report-only mode: the browser reports what it would block in its console and
8
+ # blocks nothing. It fits the app as generated (the config.js script and the module script are of the same
9
+ # origin, the favicon is a data: URL, the styles are linked, /api is of the same origin). Adapt it to what the app
10
+ # loads (an API of another origin goes into connect-src, a CDN into script-src and style-src), watch the console,
11
+ # then change the header name to Content-Security-Policy to enforce it. Admin libraries often need 'unsafe-inline'
12
+ # for styles. `frame-ancestors` replaces X-Frame-Options where both exist.
13
+ add_header Content-Security-Policy-Report-Only "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'" always;
@@ -8,6 +8,9 @@ server {
8
8
  listen 8080;
9
9
  server_name _;
10
10
 
11
+ # The Server header and the error pages name no nginx version.
12
+ server_tokens off;
13
+
11
14
  root /usr/share/nginx/html;
12
15
  index index.html;
13
16
 
@@ -28,7 +28,9 @@ export default defineConfig(({ mode }) => {
28
28
  rolldownOptions: {
29
29
  output: {
30
30
  // Runs each module after the modules it imports: a split chunk can otherwise run a DI package
31
- // before reflect-metadata (vitejs/vite#21866). It costs about 3% of the gzip size; keep it on.
31
+ // before reflect-metadata (vitejs/vite#21866). It costs about 3% of the gzip size of a small app
32
+ // and 3 to 20% of the first screen of an app with more than a thousand chunks: keep it on, or
33
+ // measure (the Vite setup guide).
32
34
  strictExecutionOrder: true,
33
35
  // React, react-admin and the router weigh about 500 KB before any application code. Two
34
36
  // vendor chunks keep the application's own chunk small, cacheable across a deploy, and
@@ -13,12 +13,12 @@
13
13
  "dependencies": {
14
14
  "@reduxjs/toolkit": "^2.13.0",
15
15
  "@tanstack/react-query": "^5.104.5",
16
- "@venizia/ardor": "0.4.8-5",
17
- "@venizia/ignis-connectors": "0.4.8-4",
18
- "@venizia/ignis-filter": "0.4.7-2",
19
- "@venizia/ignis-helpers": "0.4.8-4",
16
+ "@venizia/ardor": "0.4.8-6",
17
+ "@venizia/ignis-connectors": "0.4.8-5",
18
+ "@venizia/ignis-filter": "0.4.7-3",
19
+ "@venizia/ignis-helpers": "0.4.8-5",
20
20
  "@venizia/ignis-inversion": "0.4.7-2",
21
- "@venizia/ignis-kernel": "0.4.8-4",
21
+ "@venizia/ignis-kernel": "0.4.8-5",
22
22
  "class-variance-authority": "^0.7.1",
23
23
  "clsx": "^2.1.1",
24
24
  "ra-core": "^5.15.5",
@@ -36,7 +36,7 @@
36
36
  "@types/react": "^19",
37
37
  "@types/react-dom": "^19",
38
38
  "@venizia/dev-configs": "0.4.8-2",
39
- "@venizia/ignis-boot": "0.4.8-4",
39
+ "@venizia/ignis-boot": "0.4.8-5",
40
40
  "@vitejs/plugin-react": "^6.1.2",
41
41
  "eslint": "^10.13.0",
42
42
  "oxlint": "1.89.0",
@@ -47,11 +47,11 @@
47
47
  },
48
48
  "peers": {
49
49
  "@venizia/dev-configs": "^0.4.0 || ^0.4.8-0",
50
- "@venizia/ignis-connectors": "^0.4.8-4",
51
- "@venizia/ignis-filter": "^0.4.7-2",
52
- "@venizia/ignis-helpers": "^0.4.8-4",
50
+ "@venizia/ignis-connectors": "^0.4.8-5",
51
+ "@venizia/ignis-filter": "^0.4.7-3",
52
+ "@venizia/ignis-helpers": "^0.4.8-5",
53
53
  "@venizia/ignis-inversion": "^0.4.7-2",
54
- "@venizia/ignis-kernel": "^0.4.8-4"
54
+ "@venizia/ignis-kernel": "^0.4.8-5"
55
55
  },
56
56
  "scripts": {
57
57
  "server:dev": "vite --config .configs/builders/vite.config.mts",
@@ -5,14 +5,14 @@
5
5
  "devDependencies": {
6
6
  "@types/bun": "^1.4.3",
7
7
  "@types/react": "^19",
8
- "@venizia/ardor": "^0.4.8-5",
8
+ "@venizia/ardor": "^0.4.8-6",
9
9
  "@venizia/dev-configs": "0.4.8-2",
10
- "@venizia/ignis-boot": "0.4.8-4",
11
- "@venizia/ignis-connectors": "0.4.8-4",
12
- "@venizia/ignis-filter": "0.4.7-2",
13
- "@venizia/ignis-helpers": "0.4.8-4",
10
+ "@venizia/ignis-boot": "0.4.8-5",
11
+ "@venizia/ignis-connectors": "0.4.8-5",
12
+ "@venizia/ignis-filter": "0.4.7-3",
13
+ "@venizia/ignis-helpers": "0.4.8-5",
14
14
  "@venizia/ignis-inversion": "0.4.7-2",
15
- "@venizia/ignis-kernel": "0.4.8-4",
15
+ "@venizia/ignis-kernel": "0.4.8-5",
16
16
  "eslint": "^10.13.0",
17
17
  "oxlint": "1.89.0",
18
18
  "oxlint-tsgolint": "7.0.2004",
@@ -23,7 +23,7 @@
23
23
  "typescript": "^6.0.3"
24
24
  },
25
25
  "peerDependencies": {
26
- "@venizia/ardor": "^0.4.8-5",
26
+ "@venizia/ardor": "^0.4.8-6",
27
27
  "react": "^19.3.1",
28
28
  "react-dom": "^19.3.1",
29
29
  "react-router-dom": "^7.18.4",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@venizia/ardor",
3
- "version": "0.4.8-5",
3
+ "version": "0.4.8-6",
4
4
  "description": "ARDOR - frontend application framework for the VENIZIA family. One entry point over ardor-kernel, ardor-react and ardor-admin: IGNIS inversion of control, a REST data layer, auth and i18n providers, and the React bindings for a react-admin application.",
5
5
  "keywords": [
6
6
  "ardor",
@@ -79,21 +79,21 @@
79
79
  "prepublishOnly": "make rebuild"
80
80
  },
81
81
  "dependencies": {
82
- "@venizia/ardor-admin": "0.4.8-5",
83
- "@venizia/ardor-kernel": "0.4.8-5",
84
- "@venizia/ardor-react": "0.4.8-5"
82
+ "@venizia/ardor-admin": "0.4.8-6",
83
+ "@venizia/ardor-kernel": "0.4.8-6",
84
+ "@venizia/ardor-react": "0.4.8-6"
85
85
  },
86
86
  "devDependencies": {
87
87
  "@size-limit/preset-small-lib": "^14.2.0",
88
88
  "@types/bun": "^1.4.3",
89
89
  "@types/node": "^25.0.10",
90
90
  "@venizia/dev-configs": "^0.4.8-2",
91
- "@venizia/ignis-boot": "^0.4.8-4",
92
- "@venizia/ignis-connectors": "^0.4.8-4",
93
- "@venizia/ignis-filter": "^0.4.7-2",
94
- "@venizia/ignis-helpers": "^0.4.8-4",
91
+ "@venizia/ignis-boot": "^0.4.8-5",
92
+ "@venizia/ignis-connectors": "^0.4.8-5",
93
+ "@venizia/ignis-filter": "^0.4.7-3",
94
+ "@venizia/ignis-helpers": "^0.4.8-5",
95
95
  "@venizia/ignis-inversion": "^0.4.7-2",
96
- "@venizia/ignis-kernel": "^0.4.8-4",
96
+ "@venizia/ignis-kernel": "^0.4.8-5",
97
97
  "eslint": "^10.13.0",
98
98
  "oxlint": "1.89.0",
99
99
  "oxlint-tsgolint": "7.0.2004",
@@ -105,12 +105,12 @@
105
105
  "peerDependencies": {
106
106
  "@reduxjs/toolkit": ">=2.13.0",
107
107
  "@tanstack/react-query": ">=5.104.5",
108
- "@venizia/ignis-boot": "^0.4.8-4",
109
- "@venizia/ignis-connectors": "^0.4.8-4",
110
- "@venizia/ignis-filter": "^0.4.7-2",
111
- "@venizia/ignis-helpers": "^0.4.8-4",
108
+ "@venizia/ignis-boot": "^0.4.8-5",
109
+ "@venizia/ignis-connectors": "^0.4.8-5",
110
+ "@venizia/ignis-filter": "^0.4.7-3",
111
+ "@venizia/ignis-helpers": "^0.4.8-5",
112
112
  "@venizia/ignis-inversion": "^0.4.7-2",
113
- "@venizia/ignis-kernel": "^0.4.8-4",
113
+ "@venizia/ignis-kernel": "^0.4.8-5",
114
114
  "ra-core": ">=5.15.5",
115
115
  "ra-i18n-polyglot": ">=5.15.5",
116
116
  "react": ">=19.3.1",