@glash/cli 0.2.33 → 0.2.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/bin/glash.mjs +2 -0
- package/commands/deploy.mjs +247 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -109,6 +109,10 @@ glash deploy --asset-mode auto --asset-threshold 100mb
|
|
|
109
109
|
Every deploy prints a live monitor link. Open it to watch the black-screen deployment flow while the CLI is packaging, uploading, building, routing, and checking the live URL.
|
|
110
110
|
Public asset offload prepares assets in small batches and uses multipart uploads for large files, so image/video-heavy `public/` folders can upload without forcing the source bundle over the platform limit.
|
|
111
111
|
|
|
112
|
+
Framework/runtime checks:
|
|
113
|
+
|
|
114
|
+
`glash deploy` detects both the project framework and the runtime scripts in `package.json`. If they disagree, for example the project looks like Next.js but `start` calls `glash serve`, the CLI stops before upload and asks you to align the scripts or explicitly continue. Use `--ignore-framework-mismatch` only when the mismatch is intentional.
|
|
115
|
+
|
|
112
116
|
Large source bundles:
|
|
113
117
|
|
|
114
118
|
```bash
|
package/bin/glash.mjs
CHANGED
|
@@ -102,7 +102,9 @@ ${color.bold('FLAGS')}
|
|
|
102
102
|
--asset-threshold <size> Auto-offload threshold (default: 100mb)
|
|
103
103
|
--source-upload <mode> Source upload: auto, direct, chunked
|
|
104
104
|
--source-threshold <size> Auto-chunk source threshold (default: 90mb)
|
|
105
|
+
--source-concurrency <n> Parallel chunk uploads for large source bundles (default: 4)
|
|
105
106
|
--framework <name> Override framework detection (glashjs, next, vite, etc.)
|
|
107
|
+
--ignore-framework-mismatch Continue when detected framework and runtime scripts differ
|
|
106
108
|
--help, -h Show this help
|
|
107
109
|
--version, -v Show CLI version
|
|
108
110
|
|
package/commands/deploy.mjs
CHANGED
|
@@ -9,7 +9,7 @@ import { join, basename, extname } from 'node:path';
|
|
|
9
9
|
import { tmpdir } from 'node:os';
|
|
10
10
|
import { createReadStream, openAsBlob } from 'node:fs';
|
|
11
11
|
import { access, readFile, rm, stat } from 'node:fs/promises';
|
|
12
|
-
import { stdout } from 'node:process';
|
|
12
|
+
import { stdin, stdout } from 'node:process';
|
|
13
13
|
|
|
14
14
|
function currentCommit() {
|
|
15
15
|
try {
|
|
@@ -137,6 +137,10 @@ export async function deploy({ flags }) {
|
|
|
137
137
|
const target = flags.prod || flags.production ? 'production' : (flags.target || 'production');
|
|
138
138
|
const requestedAssetMode = assetMode(flags);
|
|
139
139
|
const projectProfile = remoteDeploy ? { framework: frameworkHint(flags), label: frameworkHint(flags) || 'auto' } : await detectProjectProfile(process.cwd(), flags);
|
|
140
|
+
if (!remoteDeploy) {
|
|
141
|
+
const compatible = await confirmFrameworkRuntimeCompatibility(projectProfile, flags);
|
|
142
|
+
if (!compatible) return fail('Deployment cancelled. Align the framework/runtime scripts or rerun with --ignore-framework-mismatch.');
|
|
143
|
+
}
|
|
140
144
|
const monitorStartedAt = Date.now();
|
|
141
145
|
const initialStatusUrl = deployMonitorUrl({ projectId, after: monitorStartedAt });
|
|
142
146
|
|
|
@@ -153,6 +157,7 @@ export async function deploy({ flags }) {
|
|
|
153
157
|
target,
|
|
154
158
|
source: remoteDeploy ? 'remote git deploy' : 'local upload',
|
|
155
159
|
framework: projectProfile.framework,
|
|
160
|
+
runtime: projectProfile.runtime?.summary || '',
|
|
156
161
|
sourceUpload: { mode: remoteDeploy ? 'remote' : 'pending', parts: 0 },
|
|
157
162
|
packagesUploaded: 0,
|
|
158
163
|
filesIncluded: 0,
|
|
@@ -218,6 +223,7 @@ export async function deploy({ flags }) {
|
|
|
218
223
|
gitBranch: gitBranch || '',
|
|
219
224
|
target,
|
|
220
225
|
framework: projectProfile.framework,
|
|
226
|
+
flags,
|
|
221
227
|
};
|
|
222
228
|
|
|
223
229
|
if (shouldUseChunkedSourceUpload(zipStat.size, flags)) {
|
|
@@ -519,10 +525,19 @@ async function uploadSourceBundleStaged(projectId, zipPath, zipStat, meta, indic
|
|
|
519
525
|
|
|
520
526
|
const partSize = sourceUploadPartSize(prepared.partSize);
|
|
521
527
|
const blob = await openAsBlob(zipPath, { type: 'application/zip' });
|
|
528
|
+
const chunks = [];
|
|
529
|
+
for (let offset = 0, partNumber = 1; offset < sizeBytes; offset += partSize, partNumber += 1) {
|
|
530
|
+
chunks.push({
|
|
531
|
+
partNumber,
|
|
532
|
+
offset,
|
|
533
|
+
end: Math.min(offset + partSize, sizeBytes),
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
|
|
522
537
|
const parts = [];
|
|
523
|
-
let
|
|
524
|
-
|
|
525
|
-
|
|
538
|
+
let uploadedBytes = 0;
|
|
539
|
+
const concurrency = sourceUploadConcurrency(meta.flags);
|
|
540
|
+
await mapWithConcurrency(chunks, concurrency, async ({ partNumber, offset, end }) => {
|
|
526
541
|
const partUrl = withQueryParam(prepared.uploadPartUrl, 'partNumber', String(partNumber));
|
|
527
542
|
const res = await fetchWithRetries(partUrl, {
|
|
528
543
|
method: 'PUT',
|
|
@@ -540,10 +555,11 @@ async function uploadSourceBundleStaged(projectId, zipPath, zipStat, meta, indic
|
|
|
540
555
|
throw new Error(`Chunked source upload part ${partNumber} did not return a multipart ETag`);
|
|
541
556
|
}
|
|
542
557
|
parts.push({ partNumber: Number(part.partNumber), etag: String(part.etag) });
|
|
543
|
-
|
|
558
|
+
uploadedBytes += (end - offset);
|
|
559
|
+
const percent = 28 + Math.round((uploadedBytes / sizeBytes) * 14);
|
|
544
560
|
indicator?.setProgress?.(percent, 'uploading');
|
|
545
|
-
|
|
546
|
-
|
|
561
|
+
});
|
|
562
|
+
parts.sort((a, b) => a.partNumber - b.partNumber);
|
|
547
563
|
|
|
548
564
|
const completed = await fetchWithRetries(prepared.completeUrl, {
|
|
549
565
|
method: 'POST',
|
|
@@ -575,6 +591,19 @@ async function uploadSourceBundleStaged(projectId, zipPath, zipStat, meta, indic
|
|
|
575
591
|
return { deployment, partsUploaded: parts.length, sha256 };
|
|
576
592
|
}
|
|
577
593
|
|
|
594
|
+
async function mapWithConcurrency(items, concurrency, worker) {
|
|
595
|
+
const limit = Math.max(1, Math.min(Number(concurrency) || 1, items.length || 1));
|
|
596
|
+
let next = 0;
|
|
597
|
+
const runners = Array.from({ length: limit }, async () => {
|
|
598
|
+
while (next < items.length) {
|
|
599
|
+
const item = items[next];
|
|
600
|
+
next += 1;
|
|
601
|
+
await worker(item);
|
|
602
|
+
}
|
|
603
|
+
});
|
|
604
|
+
await Promise.all(runners);
|
|
605
|
+
}
|
|
606
|
+
|
|
578
607
|
function sourceUploadFailure(error) {
|
|
579
608
|
const message = error?.message || String(error);
|
|
580
609
|
if (error?.status === 404 && /source-upload|Cannot POST/i.test(message)) {
|
|
@@ -629,7 +658,7 @@ function uploadTimeoutMsFor(bytes) {
|
|
|
629
658
|
function publicAssetPartSize(value) {
|
|
630
659
|
const parsed = Number(value);
|
|
631
660
|
if (Number.isFinite(parsed) && parsed >= 5 * 1024 * 1024) return Math.round(parsed);
|
|
632
|
-
return
|
|
661
|
+
return 80 * 1024 * 1024;
|
|
633
662
|
}
|
|
634
663
|
|
|
635
664
|
function assetOffloadFailure(error, assetSummary) {
|
|
@@ -678,31 +707,205 @@ function sourceUploadThresholdBytes(flags) {
|
|
|
678
707
|
function sourceUploadPartSize(value) {
|
|
679
708
|
const parsed = Number(value);
|
|
680
709
|
if (Number.isFinite(parsed) && parsed >= 5 * 1024 * 1024) return Math.round(parsed);
|
|
681
|
-
return
|
|
710
|
+
return 80 * 1024 * 1024;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
function sourceUploadConcurrency(flags = {}) {
|
|
714
|
+
const raw = flags['source-concurrency'] ?? process.env.GLASH_SOURCE_UPLOAD_CONCURRENCY ?? '4';
|
|
715
|
+
const parsed = Number(raw);
|
|
716
|
+
if (!Number.isFinite(parsed) || parsed < 1) return 4;
|
|
717
|
+
return Math.max(1, Math.min(8, Math.floor(parsed)));
|
|
682
718
|
}
|
|
683
719
|
|
|
684
720
|
async function detectProjectProfile(cwd, flags = {}) {
|
|
721
|
+
const runtime = await inspectProjectRuntime(cwd);
|
|
685
722
|
const forced = frameworkHint(flags);
|
|
686
|
-
|
|
723
|
+
const profile = forced
|
|
724
|
+
? { framework: forced, label: frameworkLabel(forced), forced: true }
|
|
725
|
+
: inferFrameworkFromRuntime(runtime);
|
|
726
|
+
return { ...profile, runtime: summarizeRuntimeCompatibility(profile, runtime) };
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
async function inspectProjectRuntime(cwd) {
|
|
687
730
|
const pkg = await readPackageJson(cwd);
|
|
688
731
|
const deps = { ...(pkg?.dependencies ?? {}), ...(pkg?.devDependencies ?? {}) };
|
|
732
|
+
const scripts = pkg?.scripts ?? {};
|
|
733
|
+
const scriptText = Object.values(scripts).join('\n');
|
|
734
|
+
const hasDockerfile = await existsAny(cwd, ['Dockerfile', 'dockerfile']);
|
|
735
|
+
const hasNextConfig = await existsAny(cwd, ['next.config.js', 'next.config.mjs', 'next.config.cjs', 'next.config.ts']);
|
|
689
736
|
const hasGlashConfig = await existsAny(cwd, ['glash.config.mjs', 'glash.config.js', 'glash.config.cjs']);
|
|
690
|
-
|
|
691
|
-
|
|
737
|
+
const scriptCalls = {
|
|
738
|
+
build: scriptFrameworkCalls(scripts.build),
|
|
739
|
+
start: scriptFrameworkCalls(scripts.start),
|
|
740
|
+
dev: scriptFrameworkCalls(scripts.dev),
|
|
741
|
+
};
|
|
742
|
+
return {
|
|
743
|
+
pkg,
|
|
744
|
+
deps,
|
|
745
|
+
scripts,
|
|
746
|
+
scriptText,
|
|
747
|
+
hasDockerfile,
|
|
748
|
+
hasNextConfig,
|
|
749
|
+
hasGlashConfig,
|
|
750
|
+
buildFramework: scriptCalls.build[0] || '',
|
|
751
|
+
startFramework: scriptCalls.start[0] || '',
|
|
752
|
+
devFramework: scriptCalls.dev[0] || '',
|
|
753
|
+
buildCalls: scriptCalls.build,
|
|
754
|
+
startCalls: scriptCalls.start,
|
|
755
|
+
devCalls: scriptCalls.dev,
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
function inferFrameworkFromRuntime(runtime) {
|
|
760
|
+
const { deps, scriptText } = runtime;
|
|
761
|
+
if (runtime.hasDockerfile) return { framework: 'dockerfile', label: 'Dockerfile' };
|
|
762
|
+
if (deps.next || runtime.hasNextConfig || /\bnext\s+(build|dev|start)\b/i.test(scriptText)) return { framework: 'next', label: 'Next.js' };
|
|
763
|
+
if (deps.glashjs || runtime.hasGlashConfig || /\bglash(?:db)?\s+(build|serve|dev|start)\b/i.test(scriptText)) return { framework: 'glashjs', label: 'glashjs' };
|
|
692
764
|
if (deps.nuxt || deps.nuxt3) return { framework: 'nuxt', label: 'Nuxt' };
|
|
693
765
|
if (deps.vite) return { framework: 'vite', label: 'Vite' };
|
|
694
766
|
if (deps['@remix-run/dev'] || deps.remix) return { framework: 'remix', label: 'Remix' };
|
|
695
767
|
if (deps.astro) return { framework: 'astro', label: 'Astro' };
|
|
696
768
|
if (deps.svelte || deps['@sveltejs/kit']) return { framework: 'sveltekit', label: 'SvelteKit' };
|
|
697
769
|
if (deps.express || deps.fastify || deps['@nestjs/core']) return { framework: 'node', label: 'Node.js' };
|
|
698
|
-
if (pkg) return { framework: 'node', label: 'Node.js' };
|
|
770
|
+
if (runtime.pkg) return { framework: 'node', label: 'Node.js' };
|
|
699
771
|
return { framework: 'static', label: 'Static' };
|
|
700
772
|
}
|
|
701
773
|
|
|
774
|
+
function summarizeRuntimeCompatibility(profile, runtime) {
|
|
775
|
+
const runtimeFramework = runtime.startFramework || runtime.buildFramework || runtime.devFramework || (runtime.hasDockerfile ? 'dockerfile' : '');
|
|
776
|
+
const buildFramework = runtime.buildFramework || '';
|
|
777
|
+
const summary = runtimeFramework
|
|
778
|
+
? `${frameworkLabel(runtimeFramework)}${runtime.startFramework ? ' start' : runtime.buildFramework ? ' build' : runtime.devFramework ? ' dev' : ''}`
|
|
779
|
+
: runtime.hasDockerfile ? 'Dockerfile' : 'auto';
|
|
780
|
+
const mismatch = frameworkRuntimeMismatch(profile.framework, runtimeFramework, buildFramework, runtime);
|
|
781
|
+
return {
|
|
782
|
+
...runtime,
|
|
783
|
+
runtimeFramework,
|
|
784
|
+
summary,
|
|
785
|
+
mismatch,
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
function frameworkRuntimeMismatch(framework, runtimeFramework, buildFramework, runtime) {
|
|
790
|
+
if (!framework || framework === 'auto' || framework === 'dockerfile' || framework === 'static') return null;
|
|
791
|
+
if (!runtimeFramework || runtimeFramework === 'dockerfile') return null;
|
|
792
|
+
if (frameworksCompatible(framework, runtimeFramework)) return null;
|
|
793
|
+
const buildStillCallsFramework = buildFramework && frameworksCompatible(framework, buildFramework);
|
|
794
|
+
if (runtime.startFramework === 'node' && buildStillCallsFramework) return null;
|
|
795
|
+
if (!runtime.startFramework && buildStillCallsFramework) return null;
|
|
796
|
+
return {
|
|
797
|
+
framework,
|
|
798
|
+
runtimeFramework,
|
|
799
|
+
buildFramework,
|
|
800
|
+
buildScript: runtime.scripts?.build || '',
|
|
801
|
+
startScript: runtime.scripts?.start || '',
|
|
802
|
+
devScript: runtime.scripts?.dev || '',
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
function frameworksCompatible(a, b) {
|
|
807
|
+
const left = normalizeFrameworkName(a);
|
|
808
|
+
const right = normalizeFrameworkName(b);
|
|
809
|
+
if (!left || !right) return false;
|
|
810
|
+
if (left === right) return true;
|
|
811
|
+
if (left === 'sveltekit' && right === 'vite') return true;
|
|
812
|
+
if (left === 'vite' && right === 'sveltekit') return true;
|
|
813
|
+
return false;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
function scriptFrameworkCalls(command = '') {
|
|
817
|
+
const text = String(command || '');
|
|
818
|
+
if (!text.trim()) return [];
|
|
819
|
+
const patterns = [
|
|
820
|
+
['next', /\bnext\s+(?:build|start|dev|export)\b/ig],
|
|
821
|
+
['glashjs', /\bglash(?:db)?\s+(?:build|serve|dev|start)\b/ig],
|
|
822
|
+
['nuxt', /\b(?:nuxt|nuxi)\s*(?:build|start|preview|dev|generate)?\b/ig],
|
|
823
|
+
['astro', /\bastro\s+(?:build|dev|preview)\b/ig],
|
|
824
|
+
['remix', /\b(?:remix-serve|remix)\b/ig],
|
|
825
|
+
['sveltekit', /\bsvelte-kit\s+(?:build|dev|preview)\b/ig],
|
|
826
|
+
['vite', /\bvite(?:\s+(?:build|preview|dev|--host))?\b/ig],
|
|
827
|
+
['node', /\b(?:node|tsx|ts-node)\b/ig],
|
|
828
|
+
['node', /\bnest\s+start\b/ig],
|
|
829
|
+
];
|
|
830
|
+
const matches = [];
|
|
831
|
+
for (const [framework, regex] of patterns) {
|
|
832
|
+
for (const match of text.matchAll(regex)) {
|
|
833
|
+
matches.push({ framework, index: match.index ?? 0 });
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
matches.sort((a, b) => a.index - b.index);
|
|
837
|
+
const out = [];
|
|
838
|
+
for (const match of matches) {
|
|
839
|
+
if (!out.includes(match.framework)) out.push(match.framework);
|
|
840
|
+
}
|
|
841
|
+
return out;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
function normalizeFrameworkName(value) {
|
|
845
|
+
const raw = String(value || '').trim().toLowerCase();
|
|
846
|
+
const aliases = {
|
|
847
|
+
glash: 'glashjs',
|
|
848
|
+
glashdb: 'glashjs',
|
|
849
|
+
nextjs: 'next',
|
|
850
|
+
svelte: 'sveltekit',
|
|
851
|
+
docker: 'dockerfile',
|
|
852
|
+
dockerfile: 'dockerfile',
|
|
853
|
+
};
|
|
854
|
+
return aliases[raw] ?? raw;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
function frameworkLabel(value) {
|
|
858
|
+
const labels = {
|
|
859
|
+
next: 'Next.js',
|
|
860
|
+
glashjs: 'glashjs',
|
|
861
|
+
nuxt: 'Nuxt',
|
|
862
|
+
vite: 'Vite',
|
|
863
|
+
remix: 'Remix',
|
|
864
|
+
astro: 'Astro',
|
|
865
|
+
sveltekit: 'SvelteKit',
|
|
866
|
+
node: 'Node.js',
|
|
867
|
+
static: 'Static',
|
|
868
|
+
dockerfile: 'Dockerfile',
|
|
869
|
+
auto: 'auto',
|
|
870
|
+
};
|
|
871
|
+
return labels[normalizeFrameworkName(value)] ?? String(value || 'auto');
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
async function confirmFrameworkRuntimeCompatibility(profile, flags = {}) {
|
|
875
|
+
const mismatch = profile?.runtime?.mismatch;
|
|
876
|
+
if (!mismatch) return true;
|
|
877
|
+
|
|
878
|
+
const detected = frameworkLabel(mismatch.framework);
|
|
879
|
+
const runtime = frameworkLabel(mismatch.runtimeFramework);
|
|
880
|
+
warn('');
|
|
881
|
+
warn('Framework/runtime mismatch detected');
|
|
882
|
+
warn(`Your framework is ${color.bold(detected)} while your runtime calls on ${color.bold(runtime)}.`);
|
|
883
|
+
if (mismatch.buildScript) warn(`build: ${color.dim(mismatch.buildScript)}`);
|
|
884
|
+
if (mismatch.startScript) warn(`start: ${color.dim(mismatch.startScript)}`);
|
|
885
|
+
warn('Consider moving to one framework, or ignore this warning to continue building.');
|
|
886
|
+
|
|
887
|
+
if (ignoreFrameworkMismatch(flags)) {
|
|
888
|
+
warn('Continuing because --ignore-framework-mismatch was set.');
|
|
889
|
+
return true;
|
|
890
|
+
}
|
|
891
|
+
if (!stdin.isTTY) return false;
|
|
892
|
+
const answer = await prompt('Ignore mismatch and continue building? [y/N] ');
|
|
893
|
+
return ['y', 'yes', 'ignore', 'continue'].includes(answer.toLowerCase());
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
function ignoreFrameworkMismatch(flags = {}) {
|
|
897
|
+
return Boolean(
|
|
898
|
+
flags['ignore-framework-mismatch']
|
|
899
|
+
|| flags['ignore-runtime-mismatch']
|
|
900
|
+
|| String(flags['framework-mismatch'] || '').toLowerCase() === 'ignore'
|
|
901
|
+
);
|
|
902
|
+
}
|
|
903
|
+
|
|
702
904
|
async function packageOverridesForProfile(cwd, profile) {
|
|
703
|
-
|
|
905
|
+
const overrides = await glashRuntimeOverrides(cwd);
|
|
906
|
+
if (profile?.framework !== 'glashjs') return overrides;
|
|
704
907
|
const pkg = await readPackageJson(cwd);
|
|
705
|
-
if (!pkg) return
|
|
908
|
+
if (!pkg) return overrides;
|
|
706
909
|
const scripts = { ...(pkg.scripts ?? {}) };
|
|
707
910
|
let changed = false;
|
|
708
911
|
if (!scripts.build) {
|
|
@@ -713,17 +916,42 @@ async function packageOverridesForProfile(cwd, profile) {
|
|
|
713
916
|
scripts.start = 'glash serve --port ${PORT:-3000}';
|
|
714
917
|
changed = true;
|
|
715
918
|
}
|
|
716
|
-
if (!changed) return
|
|
919
|
+
if (!changed) return overrides;
|
|
717
920
|
return {
|
|
921
|
+
...overrides,
|
|
718
922
|
'package.json': `${JSON.stringify({ ...pkg, scripts }, null, 2)}\n`,
|
|
719
923
|
};
|
|
720
924
|
}
|
|
721
925
|
|
|
926
|
+
const GLASH_PUBLIC_RUNTIME_FILES = [
|
|
927
|
+
'glash-sw.js',
|
|
928
|
+
'glash-offline.mjs',
|
|
929
|
+
'glash-favicon.mjs',
|
|
930
|
+
'glash-assets.manifest.json',
|
|
931
|
+
'manifest.webmanifest',
|
|
932
|
+
'favicon.svg',
|
|
933
|
+
'favicon-animated.svg',
|
|
934
|
+
];
|
|
935
|
+
|
|
936
|
+
async function glashRuntimeOverrides(cwd) {
|
|
937
|
+
const overrides = {};
|
|
938
|
+
for (const name of GLASH_PUBLIC_RUNTIME_FILES) {
|
|
939
|
+
const publicPath = `public/${name}`;
|
|
940
|
+
const candidates = [publicPath, `.glash/out/${name}`];
|
|
941
|
+
for (const candidate of candidates) {
|
|
942
|
+
try {
|
|
943
|
+
overrides[publicPath] = await readFile(join(cwd, candidate));
|
|
944
|
+
break;
|
|
945
|
+
} catch {}
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
return overrides;
|
|
949
|
+
}
|
|
950
|
+
|
|
722
951
|
function frameworkHint(flags = {}) {
|
|
723
952
|
const raw = String(flags.framework ?? flags.runtime ?? '').trim().toLowerCase();
|
|
724
953
|
if (!raw) return '';
|
|
725
|
-
const
|
|
726
|
-
const normalized = aliases[raw] ?? raw;
|
|
954
|
+
const normalized = normalizeFrameworkName(raw === 'react' ? 'vite' : raw);
|
|
727
955
|
return /^[a-z0-9._-]{1,40}$/.test(normalized) ? normalized : '';
|
|
728
956
|
}
|
|
729
957
|
|
|
@@ -1009,6 +1237,7 @@ function printDeployReport(report) {
|
|
|
1009
1237
|
['Target', report.target || 'production'],
|
|
1010
1238
|
['Source', report.source || '-'],
|
|
1011
1239
|
['Framework', report.framework || 'auto'],
|
|
1240
|
+
['Runtime', report.runtime || '-'],
|
|
1012
1241
|
['Packages uploaded', formatPackagesUploaded(report)],
|
|
1013
1242
|
['Public assets', formatAssetSummary(report.assetSummary)],
|
|
1014
1243
|
['Deployment size', formatDeploymentSize(report)],
|
package/package.json
CHANGED