@nitronjs/framework 0.3.1 → 0.3.2
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/cli/create.js +6 -3
- package/cli/njs.js +26 -23
- package/lib/Auth/Auth.js +9 -4
- package/lib/Auth/Mfa.js +518 -0
- package/lib/Build/FileAnalyzer.js +32 -4
- package/lib/Build/Manager.js +22 -7
- package/lib/Build/PropUsageAnalyzer.js +123 -12
- package/lib/Console/Commands/BuildCommand.js +2 -2
- package/lib/Console/Commands/DevCommand.js +18 -9
- package/lib/Console/Commands/MakeCommand.js +2 -2
- package/lib/Console/Commands/MigrateCommand.js +1 -1
- package/lib/Console/Commands/MigrateFreshCommand.js +1 -1
- package/lib/Console/Commands/MigrateRollbackCommand.js +1 -1
- package/lib/Console/Commands/MigrateStatusCommand.js +1 -1
- package/lib/Console/Commands/SeedCommand.js +1 -1
- package/lib/Console/Commands/StartCommand.js +2 -1
- package/lib/Console/Commands/StorageLinkCommand.js +21 -32
- package/lib/Console/Stubs/rsc-consumer.tsx +17 -1
- package/lib/Console/Stubs/vendor-dev.tsx +31 -0
- package/lib/Console/Stubs/vendor.tsx +31 -0
- package/lib/Date/DateTime.js +12 -10
- package/lib/Http/Server.js +3 -2
- package/lib/Mail/Mail.js +22 -19
- package/lib/Runtime/Entry.js +1 -1
- package/lib/View/Client/spa.js +196 -112
- package/lib/View/FlightRenderer.js +5 -1
- package/lib/View/View.js +49 -1
- package/lib/index.js +1 -0
- package/package.json +3 -1
|
@@ -94,7 +94,16 @@ class FileAnalyzer {
|
|
|
94
94
|
const imported = new Set();
|
|
95
95
|
const importedBy = new Map();
|
|
96
96
|
|
|
97
|
-
|
|
97
|
+
// Start with .tsx files, then follow imports into .ts files (barrel exports, utils, types)
|
|
98
|
+
// so the importedBy chain stays complete across .ts intermediaries.
|
|
99
|
+
const queue = [...files];
|
|
100
|
+
let i = 0;
|
|
101
|
+
|
|
102
|
+
while (i < queue.length) {
|
|
103
|
+
const file = queue[i++];
|
|
104
|
+
|
|
105
|
+
if (graph.has(file)) continue;
|
|
106
|
+
|
|
98
107
|
const meta = this.analyzeFile(file);
|
|
99
108
|
graph.set(file, meta);
|
|
100
109
|
|
|
@@ -108,6 +117,12 @@ class FileAnalyzer {
|
|
|
108
117
|
importedBy.set(resolvedPath, []);
|
|
109
118
|
}
|
|
110
119
|
importedBy.get(resolvedPath).push(file);
|
|
120
|
+
|
|
121
|
+
// If the resolved file is .ts (not .tsx) and not yet in graph, queue it
|
|
122
|
+
// so its own imports get tracked in the dependency chain.
|
|
123
|
+
if (resolvedPath.endsWith(".ts") && !graph.has(resolvedPath)) {
|
|
124
|
+
queue.push(resolvedPath);
|
|
125
|
+
}
|
|
111
126
|
}
|
|
112
127
|
}
|
|
113
128
|
}
|
|
@@ -215,7 +230,10 @@ class FileAnalyzer {
|
|
|
215
230
|
JSXFragment: () => { meta.jsx = true; }
|
|
216
231
|
});
|
|
217
232
|
|
|
218
|
-
|
|
233
|
+
// Only enforce "use client" requirement on .tsx files.
|
|
234
|
+
// .ts files (hooks, utils) are pure logic modules — they don't define components
|
|
235
|
+
// and are safe to import from either server or client code.
|
|
236
|
+
if (meta.needsClient && !meta.isClient && filePath.endsWith(".tsx")) {
|
|
219
237
|
throw this.#createError('Missing "use client"', {
|
|
220
238
|
File: path.relative(Paths.project, filePath),
|
|
221
239
|
Fix: 'Add "use client" at top'
|
|
@@ -234,7 +252,15 @@ class FileAnalyzer {
|
|
|
234
252
|
}
|
|
235
253
|
|
|
236
254
|
#extractNamedExports(path, meta) {
|
|
237
|
-
const
|
|
255
|
+
const node = path.node;
|
|
256
|
+
|
|
257
|
+
// Re-exports: export { X } from './Y' — track the source as an import
|
|
258
|
+
// so the dependency graph stays complete through barrel files.
|
|
259
|
+
if (node.source && node.source.value && node.source.value.startsWith(".")) {
|
|
260
|
+
meta.imports.add(node.source.value);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const declaration = node.declaration;
|
|
238
264
|
|
|
239
265
|
if (declaration?.type === "VariableDeclaration") {
|
|
240
266
|
for (const decl of declaration.declarations) {
|
|
@@ -344,7 +370,9 @@ class FileAnalyzer {
|
|
|
344
370
|
const resolvedPath = this.resolveImport(filePath, importPath);
|
|
345
371
|
const resolvedMeta = graph.get(resolvedPath);
|
|
346
372
|
|
|
347
|
-
|
|
373
|
+
// .ts files (hooks, utils, types) are pure logic — no server/client boundary.
|
|
374
|
+
// Only .tsx files with JSX can be server components that violate the boundary.
|
|
375
|
+
if (resolvedMeta && !resolvedMeta.isClient && resolvedPath.endsWith(".tsx")) {
|
|
348
376
|
throw this.#createError("Boundary Violation", {
|
|
349
377
|
Client: relativePath(filePath),
|
|
350
378
|
Server: relativePath(resolvedPath),
|
package/lib/Build/Manager.js
CHANGED
|
@@ -258,16 +258,30 @@ class Builder {
|
|
|
258
258
|
...(frameworkBundle.changedFiles || [])
|
|
259
259
|
]);
|
|
260
260
|
|
|
261
|
-
// Include changed client component sources so their hydration bundles rebuild
|
|
261
|
+
// Include changed client component sources so their hydration bundles rebuild.
|
|
262
|
+
// Also include any client components that transitively import the changed file,
|
|
263
|
+
// because esbuild bundles all imports into each client chunk. If a dependency
|
|
264
|
+
// changes, every client chunk that bundles it must be rebuilt too.
|
|
262
265
|
for (const bundle of [userBundle, frameworkBundle]) {
|
|
263
|
-
if (!bundle?.changedSources) continue;
|
|
266
|
+
if (!bundle?.changedSources || !bundle.meta) continue;
|
|
264
267
|
|
|
265
268
|
for (const file of bundle.changedSources) {
|
|
266
|
-
const fileMeta = bundle.meta
|
|
269
|
+
const fileMeta = bundle.meta.get(file);
|
|
267
270
|
if (fileMeta?.isClient) changedViews.add(file);
|
|
271
|
+
|
|
272
|
+
const dependents = this.#getTransitiveDependents(file, bundle.importedBy || new Map());
|
|
273
|
+
|
|
274
|
+
for (const dep of dependents) {
|
|
275
|
+
const depMeta = bundle.meta.get(dep);
|
|
276
|
+
if (depMeta?.isClient) changedViews.add(dep);
|
|
277
|
+
}
|
|
268
278
|
}
|
|
269
279
|
}
|
|
270
280
|
|
|
281
|
+
if (changedViews.size > 0) {
|
|
282
|
+
this.#cache.viewsChanged = true;
|
|
283
|
+
}
|
|
284
|
+
|
|
271
285
|
for (const file of changedViews) {
|
|
272
286
|
this.#changedFiles.add(file);
|
|
273
287
|
}
|
|
@@ -366,13 +380,13 @@ class Builder {
|
|
|
366
380
|
|
|
367
381
|
async #buildViewBundle(namespace, srcDir, outDir) {
|
|
368
382
|
if (!fs.existsSync(srcDir)) {
|
|
369
|
-
return { entries: [], layouts: [], meta: new Map(), srcDir, namespace, changedFiles: [], changedSources: new Set() };
|
|
383
|
+
return { entries: [], layouts: [], meta: new Map(), importedBy: new Map(), srcDir, namespace, changedFiles: [], changedSources: new Set() };
|
|
370
384
|
}
|
|
371
385
|
|
|
372
386
|
const { entries, layouts, meta, importedBy } = this.#analyzer.discoverEntries(srcDir);
|
|
373
387
|
|
|
374
388
|
if (!entries.length && !layouts.length) {
|
|
375
|
-
return { entries: [], layouts: [], meta: new Map(), srcDir, namespace, changedFiles: [], changedSources: new Set() };
|
|
389
|
+
return { entries: [], layouts: [], meta: new Map(), importedBy: new Map(), srcDir, namespace, changedFiles: [], changedSources: new Set() };
|
|
376
390
|
}
|
|
377
391
|
|
|
378
392
|
// Pre-scan: compute effective prop usage for server→client boundary filtering.
|
|
@@ -435,7 +449,7 @@ class Builder {
|
|
|
435
449
|
|
|
436
450
|
this.#stats[namespace === "user" ? "user" : "framework"] = entries.length;
|
|
437
451
|
|
|
438
|
-
return { entries, layouts, meta, srcDir, namespace, changedFiles, changedSources };
|
|
452
|
+
return { entries, layouts, meta, importedBy, srcDir, namespace, changedFiles, changedSources };
|
|
439
453
|
}
|
|
440
454
|
|
|
441
455
|
async #postProcessMeta(entries, srcDir, outDir) {
|
|
@@ -737,7 +751,8 @@ class Builder {
|
|
|
737
751
|
|
|
738
752
|
this.#manifest[key] = {
|
|
739
753
|
css: cssFiles,
|
|
740
|
-
isLayout: true
|
|
754
|
+
isLayout: true,
|
|
755
|
+
translationKeys: this.#collectTranslationKeys(layout, meta)
|
|
741
756
|
};
|
|
742
757
|
}
|
|
743
758
|
}
|
|
@@ -634,6 +634,7 @@ function trackVariableAccess(varName, funcPath, bodyStart) {
|
|
|
634
634
|
let usedAsWhole = false;
|
|
635
635
|
const chains = [];
|
|
636
636
|
const arrayIterations = [];
|
|
637
|
+
const derivedArrayVars = [];
|
|
637
638
|
|
|
638
639
|
funcPath.traverse({
|
|
639
640
|
Identifier(idPath) {
|
|
@@ -690,6 +691,17 @@ function trackVariableAccess(varName, funcPath, bodyStart) {
|
|
|
690
691
|
|
|
691
692
|
if (callbacks.length > 0) {
|
|
692
693
|
arrayIterations.push({ prefixChain, callbacks });
|
|
694
|
+
|
|
695
|
+
const lastCall = getLastChainedCall(outermost.parentPath);
|
|
696
|
+
|
|
697
|
+
if (lastCall.parentPath.isVariableDeclarator() &&
|
|
698
|
+
lastCall.parentPath.node.id.type === "Identifier") {
|
|
699
|
+
derivedArrayVars.push({
|
|
700
|
+
derivedName: lastCall.parentPath.node.id.name,
|
|
701
|
+
prefixChain
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
|
|
693
705
|
return;
|
|
694
706
|
}
|
|
695
707
|
|
|
@@ -710,7 +722,6 @@ function trackVariableAccess(varName, funcPath, bodyStart) {
|
|
|
710
722
|
return;
|
|
711
723
|
}
|
|
712
724
|
|
|
713
|
-
// Check direct for...of: for (const item of varName)
|
|
714
725
|
if (idPath.parentPath.isForOfStatement() &&
|
|
715
726
|
idPath.parentPath.node.right === idPath.node) {
|
|
716
727
|
const loopVar = extractForOfVariable(idPath.parentPath.node);
|
|
@@ -721,19 +732,26 @@ function trackVariableAccess(varName, funcPath, bodyStart) {
|
|
|
721
732
|
}
|
|
722
733
|
}
|
|
723
734
|
|
|
724
|
-
// Guard/truthy checks (isAdmin && ..., isAdmin ? ... : ...) need the
|
|
725
|
-
// value itself but not its sub-properties. Mark as whole usage so
|
|
726
|
-
// boolean props are kept in the filtered output.
|
|
727
735
|
if (isGuardCheck(idPath)) {
|
|
728
736
|
usedAsWhole = true;
|
|
729
737
|
return;
|
|
730
738
|
}
|
|
731
739
|
|
|
732
|
-
// Used directly (as argument, JSX attribute value, assignment, etc.)
|
|
733
740
|
usedAsWhole = true;
|
|
734
741
|
}
|
|
735
742
|
});
|
|
736
743
|
|
|
744
|
+
for (const derived of derivedArrayVars) {
|
|
745
|
+
const derivedIters = collectDerivedArrayUsage(derived.derivedName, funcPath, bodyStart);
|
|
746
|
+
|
|
747
|
+
for (const iter of derivedIters) {
|
|
748
|
+
arrayIterations.push({
|
|
749
|
+
prefixChain: derived.prefixChain,
|
|
750
|
+
...iter
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
|
|
737
755
|
if (usedAsWhole) return true;
|
|
738
756
|
if (chains.length === 0 && arrayIterations.length === 0) return false;
|
|
739
757
|
|
|
@@ -919,12 +937,83 @@ function mergeArrayUsage(tree, prefixChain, elementUsage) {
|
|
|
919
937
|
}
|
|
920
938
|
}
|
|
921
939
|
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
940
|
+
function getLastChainedCall(callExprPath) {
|
|
941
|
+
let current = callExprPath;
|
|
942
|
+
|
|
943
|
+
while (true) {
|
|
944
|
+
const parent = current.parentPath;
|
|
945
|
+
|
|
946
|
+
if (!parent || (!parent.isMemberExpression() && !parent.isOptionalMemberExpression())) break;
|
|
947
|
+
if (parent.node.object !== current.node || parent.node.computed) break;
|
|
948
|
+
|
|
949
|
+
const method = parent.node.property?.name;
|
|
950
|
+
const gp = parent.parentPath;
|
|
951
|
+
|
|
952
|
+
if ((gp.isCallExpression() || gp.isOptionalCallExpression()) &&
|
|
953
|
+
gp.node.callee === parent.node &&
|
|
954
|
+
ARRAY_METHODS.has(method)) {
|
|
955
|
+
current = gp;
|
|
956
|
+
}
|
|
957
|
+
else {
|
|
958
|
+
break;
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
return current;
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
function collectDerivedArrayUsage(derivedName, funcPath, bodyStart) {
|
|
966
|
+
const iterations = [];
|
|
967
|
+
|
|
968
|
+
funcPath.traverse({
|
|
969
|
+
Identifier(idPath) {
|
|
970
|
+
if (idPath.node.start < bodyStart) return;
|
|
971
|
+
if (idPath.node.name !== derivedName) return;
|
|
972
|
+
if (isInTypeAnnotation(idPath)) return;
|
|
973
|
+
|
|
974
|
+
if (idPath.parentPath.isObjectProperty() &&
|
|
975
|
+
idPath.parentPath.node.key === idPath.node &&
|
|
976
|
+
!idPath.parentPath.node.computed) return;
|
|
977
|
+
|
|
978
|
+
if (idPath.parentPath.isVariableDeclarator() &&
|
|
979
|
+
idPath.parentPath.node.id === idPath.node) return;
|
|
980
|
+
|
|
981
|
+
const isMember = idPath.parentPath.isMemberExpression() || idPath.parentPath.isOptionalMemberExpression();
|
|
982
|
+
|
|
983
|
+
if (isMember &&
|
|
984
|
+
idPath.parentPath.node.object === idPath.node &&
|
|
985
|
+
!idPath.parentPath.node.computed) {
|
|
986
|
+
|
|
987
|
+
const propName = idPath.parentPath.node.property?.name;
|
|
988
|
+
const grandParent = idPath.parentPath.parentPath;
|
|
989
|
+
const isCall = (grandParent.isCallExpression() || grandParent.isOptionalCallExpression()) &&
|
|
990
|
+
grandParent.node.callee === idPath.parentPath.node;
|
|
991
|
+
|
|
992
|
+
if (isCall && ARRAY_METHODS.has(propName)) {
|
|
993
|
+
const callbacks = collectArrayCallbacks(grandParent, propName);
|
|
994
|
+
|
|
995
|
+
if (callbacks.length > 0) {
|
|
996
|
+
iterations.push({ callbacks });
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
if (idPath.parentPath.isForOfStatement() &&
|
|
1004
|
+
idPath.parentPath.node.right === idPath.node) {
|
|
1005
|
+
const loopVar = extractForOfVariable(idPath.parentPath.node);
|
|
1006
|
+
|
|
1007
|
+
if (loopVar) {
|
|
1008
|
+
iterations.push({ loopVar, scopePath: idPath.parentPath });
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
});
|
|
1013
|
+
|
|
1014
|
+
return iterations;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
928
1017
|
function collectMemberChain(memberPath) {
|
|
929
1018
|
const chain = [];
|
|
930
1019
|
let current = memberPath;
|
|
@@ -976,6 +1065,7 @@ function analyzePropsIdentifier(propsName, funcPath) {
|
|
|
976
1065
|
let usedAsWhole = false;
|
|
977
1066
|
const chains = [];
|
|
978
1067
|
const arrayIterations = [];
|
|
1068
|
+
const derivedArrayVars = [];
|
|
979
1069
|
const bodyStart = funcPath.node.body?.start ?? 0;
|
|
980
1070
|
|
|
981
1071
|
funcPath.traverse({
|
|
@@ -1013,6 +1103,17 @@ function analyzePropsIdentifier(propsName, funcPath) {
|
|
|
1013
1103
|
|
|
1014
1104
|
if (callbacks.length > 0) {
|
|
1015
1105
|
arrayIterations.push({ prefixChain, callbacks });
|
|
1106
|
+
|
|
1107
|
+
const lastCall = getLastChainedCall(outermost.parentPath);
|
|
1108
|
+
|
|
1109
|
+
if (lastCall.parentPath.isVariableDeclarator() &&
|
|
1110
|
+
lastCall.parentPath.node.id.type === "Identifier") {
|
|
1111
|
+
derivedArrayVars.push({
|
|
1112
|
+
derivedName: lastCall.parentPath.node.id.name,
|
|
1113
|
+
prefixChain
|
|
1114
|
+
});
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1016
1117
|
return;
|
|
1017
1118
|
}
|
|
1018
1119
|
|
|
@@ -1033,11 +1134,21 @@ function analyzePropsIdentifier(propsName, funcPath) {
|
|
|
1033
1134
|
return;
|
|
1034
1135
|
}
|
|
1035
1136
|
|
|
1036
|
-
// props used as a whole (destructured in body, passed as arg, etc.)
|
|
1037
1137
|
usedAsWhole = true;
|
|
1038
1138
|
}
|
|
1039
1139
|
});
|
|
1040
1140
|
|
|
1141
|
+
for (const derived of derivedArrayVars) {
|
|
1142
|
+
const derivedIters = collectDerivedArrayUsage(derived.derivedName, funcPath, bodyStart);
|
|
1143
|
+
|
|
1144
|
+
for (const iter of derivedIters) {
|
|
1145
|
+
arrayIterations.push({
|
|
1146
|
+
prefixChain: derived.prefixChain,
|
|
1147
|
+
...iter
|
|
1148
|
+
});
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1041
1152
|
if (usedAsWhole) return null;
|
|
1042
1153
|
if (chains.length === 0 && arrayIterations.length === 0) return {};
|
|
1043
1154
|
|
|
@@ -21,6 +21,6 @@ if (isMain) {
|
|
|
21
21
|
const onlyArg = process.argv.find(arg => arg.startsWith("--only="));
|
|
22
22
|
const only = onlyArg?.split("=")[1] || null;
|
|
23
23
|
Build(only, false) // Production build
|
|
24
|
-
.then(success => process.
|
|
25
|
-
.catch(() => process.
|
|
24
|
+
.then(success => { process.exitCode = success ? 0 : 1; })
|
|
25
|
+
.catch(() => { process.exitCode = 1; });
|
|
26
26
|
}
|
|
@@ -150,12 +150,21 @@ class DevServer {
|
|
|
150
150
|
this.#debounce.build = setTimeout(async () => {
|
|
151
151
|
const r = await this.#build();
|
|
152
152
|
if (r.success) {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
153
|
+
// When client component JS chunks are rebuilt, the browser
|
|
154
|
+
// still has the old JS modules loaded in memory. RSC refetch
|
|
155
|
+
// only updates the React tree data, not the actual JS code.
|
|
156
|
+
// A full page reload is needed to load the new JS chunks.
|
|
157
|
+
if (r.viewsChanged) {
|
|
158
|
+
this.#send("reload", { reason: "Client component changed" });
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
const changeType = detectChangeType(filePath);
|
|
162
|
+
this.#send("change", {
|
|
163
|
+
changeType,
|
|
164
|
+
cssChanged: r.cssChanged || false,
|
|
165
|
+
file: rel
|
|
166
|
+
});
|
|
167
|
+
}
|
|
159
168
|
}
|
|
160
169
|
}, 100);
|
|
161
170
|
return;
|
|
@@ -224,9 +233,9 @@ class DevServer {
|
|
|
224
233
|
|
|
225
234
|
const exit = async () => {
|
|
226
235
|
console.log();
|
|
227
|
-
watcher.close();
|
|
236
|
+
await watcher.close();
|
|
228
237
|
await this.#stop();
|
|
229
|
-
process.
|
|
238
|
+
process.exitCode = 0;
|
|
230
239
|
};
|
|
231
240
|
|
|
232
241
|
process.on("SIGINT", exit);
|
|
@@ -245,5 +254,5 @@ export default async function Dev() {
|
|
|
245
254
|
}
|
|
246
255
|
|
|
247
256
|
if (process.argv[1]?.endsWith("DevCommand.js")) {
|
|
248
|
-
Dev().catch(e => { console.error(e); process.
|
|
257
|
+
Dev().catch(e => { console.error(e); process.exitCode = 1; });
|
|
249
258
|
}
|
|
@@ -123,9 +123,9 @@ const isMain = process.argv[1]?.endsWith("MakeCommand.js");
|
|
|
123
123
|
if (isMain) {
|
|
124
124
|
const [, , type, rawName] = process.argv;
|
|
125
125
|
make(type, rawName)
|
|
126
|
-
.then(success => process.
|
|
126
|
+
.then(success => { process.exitCode = success ? 0 : 1; })
|
|
127
127
|
.catch(err => {
|
|
128
128
|
console.error(err);
|
|
129
|
-
process.
|
|
129
|
+
process.exitCode = 1;
|
|
130
130
|
});
|
|
131
131
|
}
|
|
@@ -50,7 +50,7 @@ if (isMain) {
|
|
|
50
50
|
const shouldSeed = args.includes("--seed");
|
|
51
51
|
|
|
52
52
|
migrate({ seed: shouldSeed })
|
|
53
|
-
.then(success => process.exit(success ? 0 : 1))
|
|
53
|
+
.then(success => { process.exit(success ? 0 : 1); })
|
|
54
54
|
.catch(err => {
|
|
55
55
|
console.error(err);
|
|
56
56
|
process.exit(1);
|
|
@@ -68,7 +68,7 @@ if (isMain) {
|
|
|
68
68
|
const shouldSeed = args.includes("--seed");
|
|
69
69
|
|
|
70
70
|
migrateFresh({ seed: shouldSeed })
|
|
71
|
-
.then(success => process.exit(success ? 0 : 1))
|
|
71
|
+
.then(success => { process.exit(success ? 0 : 1); })
|
|
72
72
|
.catch(err => {
|
|
73
73
|
console.error(err);
|
|
74
74
|
process.exit(1);
|
|
@@ -36,7 +36,7 @@ export default async function status() {
|
|
|
36
36
|
const isMain = process.argv[1]?.endsWith("MigrateStatusCommand.js");
|
|
37
37
|
if (isMain) {
|
|
38
38
|
status()
|
|
39
|
-
.then(success => process.exit(success ? 0 : 1))
|
|
39
|
+
.then(success => { process.exit(success ? 0 : 1); })
|
|
40
40
|
.catch(err => {
|
|
41
41
|
console.error(err);
|
|
42
42
|
process.exit(1);
|
|
@@ -37,7 +37,7 @@ if (isMain) {
|
|
|
37
37
|
const seederName = args.find(a => !a.startsWith('--')) || null;
|
|
38
38
|
|
|
39
39
|
seed(seederName)
|
|
40
|
-
.then(success => process.exit(success ? 0 : 1))
|
|
40
|
+
.then(success => { process.exit(success ? 0 : 1); })
|
|
41
41
|
.catch(err => {
|
|
42
42
|
console.error(err);
|
|
43
43
|
process.exit(1);
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import fs from "fs";
|
|
1
|
+
import fs from "fs/promises";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import os from "os";
|
|
4
4
|
import Output from "../Output.js";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Creates a symbolic link from storage/app/public to public/storage.
|
|
8
|
+
* On Windows, uses "junction" type because it doesn't require admin privileges.
|
|
8
9
|
* @returns {Promise<boolean>}
|
|
9
10
|
*/
|
|
10
11
|
export default async function storageLink() {
|
|
@@ -12,35 +13,23 @@ export default async function storageLink() {
|
|
|
12
13
|
const target = path.join(process.cwd(), "public", "storage");
|
|
13
14
|
const linkType = os.platform() === "win32" ? "junction" : "dir";
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
fs.symlink(source, target, linkType
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
else {
|
|
31
|
-
Output.success("Symbolic link created");
|
|
32
|
-
Output.dim(` ${source} → ${target}`);
|
|
33
|
-
Output.newline();
|
|
34
|
-
resolve(true);
|
|
35
|
-
}
|
|
36
|
-
});
|
|
37
|
-
});
|
|
38
|
-
}
|
|
16
|
+
try {
|
|
17
|
+
await fs.symlink(source, target, linkType);
|
|
18
|
+
Output.success("Symbolic link created");
|
|
19
|
+
Output.dim(` ${source} → ${target}`);
|
|
20
|
+
Output.newline();
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
if (err.code === "EEXIST") {
|
|
25
|
+
Output.warn("Symbolic link already exists");
|
|
26
|
+
Output.dim(` ${source} → ${target}`);
|
|
27
|
+
Output.newline();
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
39
30
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
.catch(() => process.exit(1));
|
|
46
|
-
}
|
|
31
|
+
Output.error("Failed to create symbolic link");
|
|
32
|
+
Output.dim(` ${err.message}`);
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -39,7 +39,7 @@ function navigateWithPayload(payload: string) {
|
|
|
39
39
|
rsc.root.render(React.createElement(Root));
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
function mount() {
|
|
42
|
+
async function mount() {
|
|
43
43
|
const payload = window.__NITRON_FLIGHT__;
|
|
44
44
|
|
|
45
45
|
if (!payload) return;
|
|
@@ -47,6 +47,22 @@ function mount() {
|
|
|
47
47
|
const stream = payloadToStream(payload);
|
|
48
48
|
const rscResponse = createFromReadableStream(stream);
|
|
49
49
|
|
|
50
|
+
// Wait for the RSC response to fully resolve before hydrating.
|
|
51
|
+
//
|
|
52
|
+
// Without this, hydrateRoot starts rendering AFTER the ReadableStream
|
|
53
|
+
// has already closed. React then tries to look up component chunks
|
|
54
|
+
// that no longer exist in the stream, causing "Connection closed" errors.
|
|
55
|
+
//
|
|
56
|
+
// By awaiting here, all component modules are loaded and ready BEFORE
|
|
57
|
+
// React starts rendering. This prevents the race condition entirely.
|
|
58
|
+
try {
|
|
59
|
+
await rscResponse;
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
console.error('[rsc-consumer] RSC response failed to resolve:', err);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
50
66
|
function Root(): React.ReactNode {
|
|
51
67
|
return React.use(rscResponse) as React.ReactNode;
|
|
52
68
|
}
|
|
@@ -86,3 +86,34 @@ Object.assign(window, {
|
|
|
86
86
|
__webpack_get_script_filename__: webpackRequire.u
|
|
87
87
|
});
|
|
88
88
|
|
|
89
|
+
// Client-side translation function
|
|
90
|
+
// Reads translations from window.__NITRON_TRANSLATIONS__ injected by the server
|
|
91
|
+
(globalThis as any).__ = function(key: string, params?: Record<string, any>): string {
|
|
92
|
+
const translations = (window as any).__NITRON_TRANSLATIONS__;
|
|
93
|
+
|
|
94
|
+
if (!translations) return key;
|
|
95
|
+
|
|
96
|
+
let value = translations[key];
|
|
97
|
+
|
|
98
|
+
if (value === undefined) return key;
|
|
99
|
+
|
|
100
|
+
value = String(value);
|
|
101
|
+
|
|
102
|
+
// Replace :param placeholders with actual values
|
|
103
|
+
if (params && typeof params === "object") {
|
|
104
|
+
const keys = Object.keys(params);
|
|
105
|
+
|
|
106
|
+
for (let i = 0; i < keys.length; i++) {
|
|
107
|
+
const v = params[keys[i]];
|
|
108
|
+
|
|
109
|
+
if (v !== null && v !== undefined) {
|
|
110
|
+
value = value.split(":" + keys[i]).join(String(v));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return value;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
(globalThis as any).lang = (globalThis as any).__;
|
|
119
|
+
|
|
@@ -68,3 +68,34 @@ Object.assign(window, {
|
|
|
68
68
|
__webpack_chunk_load__: webpackChunkLoad,
|
|
69
69
|
__webpack_get_script_filename__: webpackRequire.u
|
|
70
70
|
});
|
|
71
|
+
|
|
72
|
+
// Client-side translation function
|
|
73
|
+
// Reads translations from window.__NITRON_TRANSLATIONS__ injected by the server
|
|
74
|
+
(globalThis as any).__ = function(key: string, params?: Record<string, any>): string {
|
|
75
|
+
const translations = (window as any).__NITRON_TRANSLATIONS__;
|
|
76
|
+
|
|
77
|
+
if (!translations) return key;
|
|
78
|
+
|
|
79
|
+
let value = translations[key];
|
|
80
|
+
|
|
81
|
+
if (value === undefined) return key;
|
|
82
|
+
|
|
83
|
+
value = String(value);
|
|
84
|
+
|
|
85
|
+
// Replace :param placeholders with actual values
|
|
86
|
+
if (params && typeof params === "object") {
|
|
87
|
+
const keys = Object.keys(params);
|
|
88
|
+
|
|
89
|
+
for (let i = 0; i < keys.length; i++) {
|
|
90
|
+
const v = params[keys[i]];
|
|
91
|
+
|
|
92
|
+
if (v !== null && v !== undefined) {
|
|
93
|
+
value = value.split(":" + keys[i]).join(String(v));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return value;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
(globalThis as any).lang = (globalThis as any).__;
|