@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.
- package/dist/codemod/cli.d.ts +10 -1
- package/dist/codemod/cli.js +113 -22
- package/dist/codemod/files.d.ts +1 -1
- package/dist/codemod/files.js +9 -5
- package/dist/presets/app/files/.configs/builders/Dockerfile +5 -0
- package/dist/presets/app/files/.configs/builders/docker-entrypoint.d/40-runtime-config.sh +5 -3
- package/dist/presets/app/files/.configs/builders/docker-entrypoint.d/45-api-proxy.sh +19 -0
- package/dist/presets/app/files/.configs/builders/nginx-snippets/api-proxy.conf +4 -0
- package/dist/presets/app/files/.configs/builders/nginx-snippets/security-headers.conf +7 -5
- package/dist/presets/app/files/.configs/builders/nginx.conf.template +3 -0
- package/dist/presets/app/files/.configs/builders/vite.config.mts +3 -1
- package/dist/presets/app/preset.json +10 -10
- package/dist/presets/lib/preset.json +7 -7
- package/package.json +14 -14
package/dist/codemod/cli.d.ts
CHANGED
|
@@ -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
|
package/dist/codemod/cli.js
CHANGED
|
@@ -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
|
|
118
|
-
|
|
119
|
-
:
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
|
|
131
|
-
|
|
163
|
+
catch (error) {
|
|
164
|
+
failed.push({ path, reason: error instanceof Error ? error.message : String(error) });
|
|
132
165
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
|
|
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
|
-
|
|
147
|
-
const
|
|
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 =
|
|
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;
|
package/dist/codemod/files.d.ts
CHANGED
|
@@ -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[];
|
package/dist/codemod/files.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process';
|
|
2
|
-
import {
|
|
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
|
-
|
|
20
|
-
|
|
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) =>
|
|
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
|
-
#
|
|
14
|
-
#
|
|
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
|
-
|
|
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
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
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;
|
|
@@ -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
|
|
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-
|
|
17
|
-
"@venizia/ignis-connectors": "0.4.8-
|
|
18
|
-
"@venizia/ignis-filter": "0.4.7-
|
|
19
|
-
"@venizia/ignis-helpers": "0.4.8-
|
|
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-
|
|
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-
|
|
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-
|
|
51
|
-
"@venizia/ignis-filter": "^0.4.7-
|
|
52
|
-
"@venizia/ignis-helpers": "^0.4.8-
|
|
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-
|
|
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-
|
|
8
|
+
"@venizia/ardor": "^0.4.8-6",
|
|
9
9
|
"@venizia/dev-configs": "0.4.8-2",
|
|
10
|
-
"@venizia/ignis-boot": "0.4.8-
|
|
11
|
-
"@venizia/ignis-connectors": "0.4.8-
|
|
12
|
-
"@venizia/ignis-filter": "0.4.7-
|
|
13
|
-
"@venizia/ignis-helpers": "0.4.8-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
83
|
-
"@venizia/ardor-kernel": "0.4.8-
|
|
84
|
-
"@venizia/ardor-react": "0.4.8-
|
|
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-
|
|
92
|
-
"@venizia/ignis-connectors": "^0.4.8-
|
|
93
|
-
"@venizia/ignis-filter": "^0.4.7-
|
|
94
|
-
"@venizia/ignis-helpers": "^0.4.8-
|
|
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-
|
|
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-
|
|
109
|
-
"@venizia/ignis-connectors": "^0.4.8-
|
|
110
|
-
"@venizia/ignis-filter": "^0.4.7-
|
|
111
|
-
"@venizia/ignis-helpers": "^0.4.8-
|
|
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-
|
|
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",
|