@jsonpages/cli 3.0.47 → 3.0.48
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/assets/src_tenant_alpha.sh +233 -76
- package/package.json +1 -1
|
@@ -439,13 +439,14 @@ END_OF_FILE_CONTENT
|
|
|
439
439
|
echo "Creating src/components/SaveDrawer/SaveDrawer.tsx..."
|
|
440
440
|
cat << 'END_OF_FILE_CONTENT' > "src/components/SaveDrawer/SaveDrawer.tsx"
|
|
441
441
|
/**
|
|
442
|
-
* SaveDrawer — Identical to prototype DopaDrawer.
|
|
442
|
+
* SaveDrawer — Identical to prototype DopaDrawer (dopa-save).
|
|
443
443
|
* Bottom sheet for Cloud save; driven by real SSE (save-stream).
|
|
444
|
-
* Done: "
|
|
444
|
+
* Done: countdown 3s + "Chiusura in Xs" + Chiudi + Open site → auto-close. Error: Annulla + Retry.
|
|
445
445
|
*/
|
|
446
446
|
import { useState } from 'react';
|
|
447
447
|
import { cn } from '@/lib/utils';
|
|
448
448
|
import { Separator } from '@/components/ui/separator';
|
|
449
|
+
import { Progress } from '@/components/ui/progress';
|
|
449
450
|
import { DeployNode } from './DeployNode';
|
|
450
451
|
import { DeployConnector } from './DeployConnector';
|
|
451
452
|
import { Particles, BuildBars, SuccessBurst, ElapsedTimer } from './Visuals';
|
|
@@ -464,7 +465,18 @@ export interface SaveDrawerProps {
|
|
|
464
465
|
export function SaveDrawer({ cloudUrl, apiKey, state, slug, onClose }: SaveDrawerProps) {
|
|
465
466
|
const [burst, setBurst] = useState(false);
|
|
466
467
|
|
|
467
|
-
const {
|
|
468
|
+
const {
|
|
469
|
+
phase,
|
|
470
|
+
currentStep,
|
|
471
|
+
stepState,
|
|
472
|
+
doneSteps,
|
|
473
|
+
progress,
|
|
474
|
+
deployUrl,
|
|
475
|
+
countdown,
|
|
476
|
+
logs,
|
|
477
|
+
errorMessage,
|
|
478
|
+
retry,
|
|
479
|
+
} = useSaveStream({
|
|
468
480
|
cloudUrl,
|
|
469
481
|
apiKey,
|
|
470
482
|
state,
|
|
@@ -567,8 +579,12 @@ export function SaveDrawer({ cloudUrl, apiKey, state, slug, onClose }: SaveDrawe
|
|
|
567
579
|
</div>
|
|
568
580
|
) : isError ? (
|
|
569
581
|
<>
|
|
570
|
-
<p className="text-base font-semibold text-white leading-snug">
|
|
571
|
-
|
|
582
|
+
<p className="text-base font-semibold text-white leading-snug">
|
|
583
|
+
Deploy failed at build.
|
|
584
|
+
</p>
|
|
585
|
+
<p className="text-sm text-white/35 mt-0.5">
|
|
586
|
+
{errorMessage ?? 'Check your Vercel logs or retry below'}
|
|
587
|
+
</p>
|
|
572
588
|
</>
|
|
573
589
|
) : currentStep ? (
|
|
574
590
|
<>
|
|
@@ -580,7 +596,24 @@ export function SaveDrawer({ cloudUrl, apiKey, state, slug, onClose }: SaveDrawe
|
|
|
580
596
|
</div>
|
|
581
597
|
|
|
582
598
|
<div className="flex flex-col items-end gap-1.5 shrink-0 ml-6">
|
|
583
|
-
{
|
|
599
|
+
{isDone ? (
|
|
600
|
+
<div className="flex flex-col items-end gap-1.5 save-fade-up">
|
|
601
|
+
<span
|
|
602
|
+
className="font-mono text-xs text-emerald-400 font-semibold"
|
|
603
|
+
aria-live="polite"
|
|
604
|
+
>
|
|
605
|
+
Chiusura in {countdown}s
|
|
606
|
+
</span>
|
|
607
|
+
<div className="w-24 h-0.5 bg-white/10 rounded-full overflow-hidden">
|
|
608
|
+
<div
|
|
609
|
+
className="h-full bg-emerald-400 rounded-full countdown-bar"
|
|
610
|
+
style={{ boxShadow: '0 0 6px #34d39988' }}
|
|
611
|
+
/>
|
|
612
|
+
</div>
|
|
613
|
+
</div>
|
|
614
|
+
) : (
|
|
615
|
+
<ElapsedTimer running={isRunning} />
|
|
616
|
+
)}
|
|
584
617
|
</div>
|
|
585
618
|
</div>
|
|
586
619
|
|
|
@@ -608,12 +641,21 @@ export function SaveDrawer({ cloudUrl, apiKey, state, slug, onClose }: SaveDrawe
|
|
|
608
641
|
|
|
609
642
|
<Separator className="mb-5 bg-white/[0.06]" />
|
|
610
643
|
|
|
644
|
+
{isRunning && currentStep && (logs[currentStep.id]?.length ?? 0) > 0 && (
|
|
645
|
+
<div className="mb-4 max-h-16 overflow-y-auto rounded-md bg-white/[0.04] px-3 py-2 font-mono text-[11px] text-white/50 leading-relaxed">
|
|
646
|
+
{logs[currentStep.id]?.map((line, i) => (
|
|
647
|
+
<div key={i}>{line}</div>
|
|
648
|
+
))}
|
|
649
|
+
</div>
|
|
650
|
+
)}
|
|
651
|
+
|
|
611
652
|
<div className="flex items-center gap-4">
|
|
612
|
-
<div className="flex-1
|
|
613
|
-
<
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
653
|
+
<div className="flex-1">
|
|
654
|
+
<Progress
|
|
655
|
+
value={progress}
|
|
656
|
+
className="h-[2px] bg-white/[0.06]"
|
|
657
|
+
indicatorClassName="transition-none"
|
|
658
|
+
indicatorStyle={{
|
|
617
659
|
background: `linear-gradient(90deg, ${DEPLOY_STEPS[0].color}, ${activeColor})`,
|
|
618
660
|
}}
|
|
619
661
|
/>
|
|
@@ -624,17 +666,19 @@ export function SaveDrawer({ cloudUrl, apiKey, state, slug, onClose }: SaveDrawe
|
|
|
624
666
|
<div className="flex gap-2 save-fade-up">
|
|
625
667
|
<button
|
|
626
668
|
type="button"
|
|
627
|
-
onClick={
|
|
628
|
-
className="px-3 py-1.5 rounded-md
|
|
669
|
+
onClick={onClose}
|
|
670
|
+
className="px-3 py-1.5 rounded-md border border-white/20 text-white/90 text-sm hover:bg-white/10"
|
|
629
671
|
>
|
|
630
|
-
|
|
672
|
+
Chiudi
|
|
631
673
|
</button>
|
|
632
674
|
<button
|
|
633
675
|
type="button"
|
|
634
|
-
onClick={
|
|
635
|
-
|
|
676
|
+
onClick={() =>
|
|
677
|
+
deployUrl ? window.open(deployUrl, '_blank') : window.location.reload()
|
|
678
|
+
}
|
|
679
|
+
className="px-3 py-1.5 rounded-md bg-emerald-400 text-zinc-900 text-sm font-semibold hover:bg-emerald-300 gap-1.5 inline-flex items-center"
|
|
636
680
|
>
|
|
637
|
-
|
|
681
|
+
<span aria-hidden>↗</span> Open site
|
|
638
682
|
</button>
|
|
639
683
|
</div>
|
|
640
684
|
)}
|
|
@@ -875,7 +919,9 @@ END_OF_FILE_CONTENT
|
|
|
875
919
|
echo "Creating src/components/SaveDrawer/useSaveStream.ts..."
|
|
876
920
|
cat << 'END_OF_FILE_CONTENT' > "src/components/SaveDrawer/useSaveStream.ts"
|
|
877
921
|
/**
|
|
878
|
-
* useSaveStream — Same API as prototype useSaveOrchestrator,
|
|
922
|
+
* useSaveStream — Same API as prototype useSaveOrchestrator, driven by SSE from save-stream.
|
|
923
|
+
* Event handling aligned with CreateTenantFlow (parseSSE, step/log/error/done, post-loop, try/catch).
|
|
924
|
+
* Payload is read from a ref at effect start so the effect does not depend on state.page (avoids re-run and stream kill).
|
|
879
925
|
*/
|
|
880
926
|
import { useState, useCallback, useEffect, useRef } from 'react';
|
|
881
927
|
import type { ProjectState } from '@jsonpages/core';
|
|
@@ -913,15 +959,25 @@ export function useSaveStream({
|
|
|
913
959
|
apiKey,
|
|
914
960
|
state,
|
|
915
961
|
slug,
|
|
916
|
-
onClose
|
|
962
|
+
onClose,
|
|
917
963
|
}: UseSaveStreamOptions) {
|
|
918
964
|
const [phase, setPhase] = useState<DeployPhase>('running');
|
|
919
965
|
const [doneSteps, setDoneSteps] = useState<StepId[]>([]);
|
|
920
966
|
const [activeStepId, setActiveStepId] = useState<StepId | null>('commit');
|
|
921
967
|
const [progress, setProgress] = useState(0);
|
|
968
|
+
const [deployUrl, setDeployUrl] = useState<string | null>(null);
|
|
969
|
+
const [countdown, setCountdown] = useState(3);
|
|
970
|
+
const [logs, setLogs] = useState<Record<string, string[]>>({});
|
|
971
|
+
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
|
922
972
|
const progressRef = useRef(0);
|
|
923
973
|
const [runId, setRunId] = useState(0);
|
|
924
974
|
|
|
975
|
+
const payloadRef = useRef<{ path: string; content: unknown; message: string } | null>(null);
|
|
976
|
+
|
|
977
|
+
const addLog = useCallback((stepId: string, message: string) => {
|
|
978
|
+
setLogs((prev) => ({ ...prev, [stepId]: [...(prev[stepId] || []), message] }));
|
|
979
|
+
}, []);
|
|
980
|
+
|
|
925
981
|
const stepState = useCallback(
|
|
926
982
|
(i: number): StepState => {
|
|
927
983
|
const step = DEPLOY_STEPS[i];
|
|
@@ -937,81 +993,113 @@ export function useSaveStream({
|
|
|
937
993
|
|
|
938
994
|
useEffect(() => {
|
|
939
995
|
let cancelled = false;
|
|
996
|
+
let gotDone = false;
|
|
940
997
|
setPhase('running');
|
|
941
998
|
setDoneSteps([]);
|
|
942
999
|
setActiveStepId('commit');
|
|
943
1000
|
setProgress(0);
|
|
1001
|
+
setDeployUrl(null);
|
|
1002
|
+
setCountdown(3);
|
|
1003
|
+
setLogs({});
|
|
1004
|
+
setErrorMessage(null);
|
|
944
1005
|
progressRef.current = 0;
|
|
945
1006
|
|
|
1007
|
+
payloadRef.current = {
|
|
1008
|
+
path: `src/data/pages/${slug}.json`,
|
|
1009
|
+
content: state.page,
|
|
1010
|
+
message: `Content update for ${slug} via Visual Editor`,
|
|
1011
|
+
};
|
|
1012
|
+
|
|
946
1013
|
const url = `${cloudUrl.replace(/\/$/, '')}/save-stream`;
|
|
947
1014
|
|
|
948
1015
|
const run = async () => {
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
'Content-Type': 'application/json',
|
|
953
|
-
Authorization: `Bearer ${apiKey}`,
|
|
954
|
-
},
|
|
955
|
-
body: JSON.stringify({
|
|
956
|
-
path: `src/data/pages/${slug}.json`,
|
|
957
|
-
content: state.page,
|
|
958
|
-
message: `Content update for ${slug} via Visual Editor`,
|
|
959
|
-
}),
|
|
960
|
-
});
|
|
1016
|
+
try {
|
|
1017
|
+
const payload = payloadRef.current;
|
|
1018
|
+
if (!payload || cancelled) return;
|
|
961
1019
|
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
1020
|
+
const res = await fetch(url, {
|
|
1021
|
+
method: 'POST',
|
|
1022
|
+
headers: {
|
|
1023
|
+
'Content-Type': 'application/json',
|
|
1024
|
+
Authorization: `Bearer ${apiKey}`,
|
|
1025
|
+
},
|
|
1026
|
+
body: JSON.stringify(payload),
|
|
1027
|
+
});
|
|
1028
|
+
|
|
1029
|
+
if (!res.ok || !res.body) {
|
|
1030
|
+
if (!cancelled) {
|
|
1031
|
+
setPhase('error');
|
|
1032
|
+
setErrorMessage('Request failed');
|
|
1033
|
+
}
|
|
1034
|
+
return;
|
|
1035
|
+
}
|
|
966
1036
|
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
1037
|
+
const reader = res.body.getReader();
|
|
1038
|
+
const dec = new TextDecoder();
|
|
1039
|
+
let buffer = '';
|
|
970
1040
|
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
1041
|
+
while (true) {
|
|
1042
|
+
const { value, done: streamDone } = await reader.read();
|
|
1043
|
+
if (streamDone || cancelled) break;
|
|
1044
|
+
buffer += dec.decode(value, { stream: true });
|
|
1045
|
+
const parts = buffer.split('\n\n');
|
|
1046
|
+
buffer = parts.pop() || '';
|
|
977
1047
|
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
1048
|
+
for (const part of parts) {
|
|
1049
|
+
const parsed = parseSSE(part + '\n\n');
|
|
1050
|
+
for (const { event: ev, data: raw } of parsed) {
|
|
1051
|
+
if (cancelled) return;
|
|
1052
|
+
try {
|
|
1053
|
+
const data = JSON.parse(raw) as Record<string, unknown>;
|
|
1054
|
+
if (ev === 'step') {
|
|
1055
|
+
const id = data.id as StepId | undefined;
|
|
1056
|
+
const status = data.status as string | undefined;
|
|
1057
|
+
if (id && status === 'running') {
|
|
1058
|
+
setActiveStepId(id);
|
|
1059
|
+
const idx = DEPLOY_STEPS.findIndex((s) => s.id === id);
|
|
1060
|
+
if (idx >= 0) progressRef.current = Math.round((idx / DEPLOY_STEPS.length) * 100);
|
|
1061
|
+
setProgress(progressRef.current);
|
|
1062
|
+
}
|
|
1063
|
+
if (id && status === 'done') {
|
|
1064
|
+
setDoneSteps((prev) => [...prev, id].filter(Boolean));
|
|
1065
|
+
setActiveStepId(null);
|
|
1066
|
+
const idx = DEPLOY_STEPS.findIndex((s) => s.id === id);
|
|
1067
|
+
progressRef.current = idx >= 0 ? Math.round(((idx + 1) / DEPLOY_STEPS.length) * 100) : 100;
|
|
1068
|
+
setProgress(progressRef.current);
|
|
1069
|
+
}
|
|
1070
|
+
} else if (ev === 'log' && data.stepId && data.message) {
|
|
1071
|
+
addLog(data.stepId as string, data.message as string);
|
|
1072
|
+
} else if (ev === 'error') {
|
|
1073
|
+
if (!cancelled) {
|
|
1074
|
+
setErrorMessage((data.message as string) || 'Errore');
|
|
1075
|
+
setPhase('error');
|
|
1076
|
+
}
|
|
1077
|
+
return;
|
|
1078
|
+
} else if (ev === 'done') {
|
|
1079
|
+
gotDone = true;
|
|
1080
|
+
setDeployUrl((data.deployUrl as string) ?? null);
|
|
1081
|
+
setProgress(100);
|
|
1082
|
+
setDoneSteps(['commit', 'push', 'build', 'live']);
|
|
995
1083
|
setActiveStepId(null);
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
setProgress(progressRef.current);
|
|
1084
|
+
setPhase('done');
|
|
1085
|
+
return;
|
|
999
1086
|
}
|
|
1000
|
-
}
|
|
1001
|
-
|
|
1002
|
-
return;
|
|
1003
|
-
} else if (ev === 'done') {
|
|
1004
|
-
setProgress(100);
|
|
1005
|
-
setDoneSteps(['commit', 'push', 'build', 'live']);
|
|
1006
|
-
setActiveStepId(null);
|
|
1007
|
-
setPhase('done');
|
|
1008
|
-
return;
|
|
1087
|
+
} catch {
|
|
1088
|
+
// ignore parse errors
|
|
1009
1089
|
}
|
|
1010
|
-
} catch {
|
|
1011
|
-
// ignore
|
|
1012
1090
|
}
|
|
1013
1091
|
}
|
|
1014
1092
|
}
|
|
1093
|
+
|
|
1094
|
+
if (!cancelled && !gotDone) {
|
|
1095
|
+
setErrorMessage('Operazione interrotta o build non completato.');
|
|
1096
|
+
setPhase('error');
|
|
1097
|
+
}
|
|
1098
|
+
} catch (e) {
|
|
1099
|
+
if (!cancelled) {
|
|
1100
|
+
setErrorMessage(e instanceof Error ? e.message : 'Errore');
|
|
1101
|
+
setPhase('error');
|
|
1102
|
+
}
|
|
1015
1103
|
}
|
|
1016
1104
|
};
|
|
1017
1105
|
|
|
@@ -1019,7 +1107,23 @@ export function useSaveStream({
|
|
|
1019
1107
|
return () => {
|
|
1020
1108
|
cancelled = true;
|
|
1021
1109
|
};
|
|
1022
|
-
}, [cloudUrl, apiKey, slug,
|
|
1110
|
+
}, [cloudUrl, apiKey, slug, runId, addLog]);
|
|
1111
|
+
|
|
1112
|
+
useEffect(() => {
|
|
1113
|
+
if (phase !== 'done') return;
|
|
1114
|
+
setCountdown(3);
|
|
1115
|
+
const iv = setInterval(() => {
|
|
1116
|
+
setCountdown((s) => {
|
|
1117
|
+
if (s <= 1) {
|
|
1118
|
+
clearInterval(iv);
|
|
1119
|
+
onClose();
|
|
1120
|
+
return 0;
|
|
1121
|
+
}
|
|
1122
|
+
return s - 1;
|
|
1123
|
+
});
|
|
1124
|
+
}, 1000);
|
|
1125
|
+
return () => clearInterval(iv);
|
|
1126
|
+
}, [phase, onClose]);
|
|
1023
1127
|
|
|
1024
1128
|
const retry = useCallback(() => setRunId((r) => r + 1), []);
|
|
1025
1129
|
|
|
@@ -1029,6 +1133,10 @@ export function useSaveStream({
|
|
|
1029
1133
|
stepState,
|
|
1030
1134
|
doneSteps,
|
|
1031
1135
|
progress,
|
|
1136
|
+
deployUrl,
|
|
1137
|
+
countdown,
|
|
1138
|
+
logs,
|
|
1139
|
+
errorMessage,
|
|
1032
1140
|
retry,
|
|
1033
1141
|
};
|
|
1034
1142
|
}
|
|
@@ -3871,6 +3979,46 @@ export { Label }
|
|
|
3871
3979
|
|
|
3872
3980
|
|
|
3873
3981
|
|
|
3982
|
+
END_OF_FILE_CONTENT
|
|
3983
|
+
echo "Creating src/components/ui/progress.tsx..."
|
|
3984
|
+
cat << 'END_OF_FILE_CONTENT' > "src/components/ui/progress.tsx"
|
|
3985
|
+
import * as React from 'react';
|
|
3986
|
+
import { cn } from '@/lib/utils';
|
|
3987
|
+
|
|
3988
|
+
interface ProgressProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'children'> {
|
|
3989
|
+
value?: number | null;
|
|
3990
|
+
indicatorClassName?: string;
|
|
3991
|
+
indicatorStyle?: React.CSSProperties;
|
|
3992
|
+
}
|
|
3993
|
+
|
|
3994
|
+
const Progress = React.forwardRef<HTMLDivElement, ProgressProps>(
|
|
3995
|
+
({ className, value, indicatorClassName, indicatorStyle, ...props }, ref) => (
|
|
3996
|
+
<div
|
|
3997
|
+
ref={ref}
|
|
3998
|
+
role="progressbar"
|
|
3999
|
+
aria-valuenow={value ?? 0}
|
|
4000
|
+
aria-valuemin={0}
|
|
4001
|
+
aria-valuemax={100}
|
|
4002
|
+
className={cn('relative h-2 w-full overflow-hidden rounded-full bg-secondary', className)}
|
|
4003
|
+
{...props}
|
|
4004
|
+
>
|
|
4005
|
+
<div
|
|
4006
|
+
className={cn(
|
|
4007
|
+
'h-full w-full flex-1 bg-primary transition-all duration-700 ease-out',
|
|
4008
|
+
indicatorClassName
|
|
4009
|
+
)}
|
|
4010
|
+
style={{
|
|
4011
|
+
transform: `translateX(-${100 - (value ?? 0)}%)`,
|
|
4012
|
+
...indicatorStyle,
|
|
4013
|
+
}}
|
|
4014
|
+
/>
|
|
4015
|
+
</div>
|
|
4016
|
+
)
|
|
4017
|
+
);
|
|
4018
|
+
Progress.displayName = 'Progress';
|
|
4019
|
+
|
|
4020
|
+
export { Progress };
|
|
4021
|
+
|
|
3874
4022
|
END_OF_FILE_CONTENT
|
|
3875
4023
|
echo "Creating src/components/ui/select.tsx..."
|
|
3876
4024
|
cat << 'END_OF_FILE_CONTENT' > "src/components/ui/select.tsx"
|
|
@@ -5340,6 +5488,15 @@ html {
|
|
|
5340
5488
|
to { stroke-dashoffset: 0; }
|
|
5341
5489
|
}
|
|
5342
5490
|
|
|
5491
|
+
/* Countdown drain — 3s linear (dopa-save identical) */
|
|
5492
|
+
.countdown-bar {
|
|
5493
|
+
animation: countdown-drain 3s linear forwards;
|
|
5494
|
+
}
|
|
5495
|
+
@keyframes countdown-drain {
|
|
5496
|
+
from { width: 100%; }
|
|
5497
|
+
to { width: 0%; }
|
|
5498
|
+
}
|
|
5499
|
+
|
|
5343
5500
|
|
|
5344
5501
|
END_OF_FILE_CONTENT
|
|
5345
5502
|
mkdir -p "src/lib"
|