@solcreek/cli 0.4.8 → 0.4.9

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.
@@ -4,7 +4,7 @@ import { existsSync, readFileSync, writeFileSync, readdirSync, statSync, rmSync
4
4
  // ajv is lazy-imported only when --template --data is used (avoid top-level crash if deps missing)
5
5
  import { join, resolve } from "node:path";
6
6
  import { execSync, execFileSync } from "node:child_process";
7
- import { CreekClient, CreekAuthError, isSSRFramework, getSSRServerEntry, getClientAssetsDir, getDefaultBuildOutput, detectFramework, resolveConfig, formatDetectionSummary, resolvedConfigToResources, resolvedConfigToBindingRequirements, ConfigNotFoundError, getSSRServerDir, collectServerFiles, isPreBundledFramework, detectAstroCloudflareBuild, detectNextjsMode, detectMonorepo, } from "@solcreek/sdk";
7
+ import { CreekClient, CreekAuthError, isSSRFramework, getSSRServerEntry, getClientAssetsDir, getDefaultBuildOutput, detectFramework, resolveConfig, formatDetectionSummary, resolvedConfigToResources, resolvedConfigToBindingRequirements, ConfigNotFoundError, getSSRServerDir, collectServerFiles, isPreBundledFramework, detectAstroCloudflareBuild, detectNextjsMode, detectMonorepo, planDeploy, } from "@solcreek/sdk";
8
8
  import { getToken, getApiUrl } from "../utils/config.js";
9
9
  import { collectAssets } from "../utils/bundle.js";
10
10
  import { bundleSSRServer } from "../utils/ssr-bundle.js";
@@ -724,79 +724,92 @@ async function deploySandbox(cwd, skipBuild, jsonMode = false, resolved, tos) {
724
724
  const outputDir = useAdapterOutput
725
725
  ? resolve(cwd, ".creek/adapter-output")
726
726
  : resolve(cwd, resolved?.buildOutput ?? getDefaultBuildOutput(framework));
727
- if (!existsSync(outputDir)) {
728
- consola.error(`Build output not found: ${outputDir}`);
729
- if (framework) {
730
- consola.info(`Expected output for ${framework}: ${getDefaultBuildOutput(framework)}`);
731
- }
727
+ // Whether buildOutput exists is now an INPUT to planDeploy, not a
728
+ // hard precondition pure-Worker projects legitimately have no
729
+ // build output directory, and planDeploy returns an explicit error
730
+ // for the cases that genuinely need one.
731
+ // Resolve deploy shape via the SDK's pure planner. All branching
732
+ // (SPA vs SSR vs Worker, with-or-without coexisting static assets,
733
+ // bundled vs source worker entry) lives in deploy-plan.ts and is
734
+ // table-tested. Don't re-derive these conditions inline.
735
+ const planResult = planDeploy({
736
+ framework,
737
+ workerEntry: resolved?.workerEntry ?? null,
738
+ workerEntryExists: !!resolved?.workerEntry &&
739
+ existsSync(resolve(cwd, resolved.workerEntry)),
740
+ buildOutput: resolved?.buildOutput ?? "dist",
741
+ buildOutputExists: existsSync(outputDir),
742
+ astroCF: null, // sandbox path doesn't run post-build adapter detection (yet)
743
+ });
744
+ if (!planResult.ok) {
745
+ consola.error(planResult.reason);
732
746
  process.exit(1);
733
747
  }
734
- // Collect assets + determine render mode.
735
- //
736
- // Three shapes, mirroring the authenticated deploy path:
737
- // 1. Framework-detected SSR (Astro/Nuxt/etc.) → bundle the framework's
738
- // server entry, upload dist/ as assets.
739
- // 2. User-declared Worker (`[build].worker` in creek.toml, no framework)
740
- // → bundle the Worker entry with esbuild, serverFiles = { worker.js }.
741
- // `dist/` assets are skipped for now — the "Workers + Static Assets
742
- // coexist" zero-config pattern is tracked separately and requires
743
- // build-pipeline changes; until it lands, users inline HTML/JS/CSS
744
- // into the Worker (see docs).
745
- // 3. Neither → plain SPA/static, everything goes as assets.
746
- const isSSR = isSSRFramework(framework);
747
- const isWorker = !framework && !!resolved?.workerEntry;
748
- const renderMode = isWorker ? "worker" : (isSSR ? "ssr" : "spa");
748
+ const plan = planResult.plan;
749
+ const renderMode = plan.renderMode;
749
750
  let clientAssets = {};
750
751
  let fileList = [];
751
- if (!isWorker) {
752
- let clientAssetsDir;
753
- if (useAdapterOutput) {
754
- clientAssetsDir = resolve(outputDir, "assets");
755
- }
756
- else {
757
- clientAssetsDir = outputDir;
758
- if (isSSR && framework) {
759
- const subdir = getClientAssetsDir(framework);
760
- if (subdir)
761
- clientAssetsDir = resolve(outputDir, subdir);
762
- }
752
+ if (plan.assets.enabled && plan.assets.dir) {
753
+ let clientAssetsDir = useAdapterOutput
754
+ ? resolve(outputDir, "assets")
755
+ : resolve(cwd, plan.assets.dir);
756
+ if (!useAdapterOutput && isSSRFramework(framework) && framework) {
757
+ const subdir = getClientAssetsDir(framework);
758
+ if (subdir)
759
+ clientAssetsDir = resolve(clientAssetsDir, subdir);
763
760
  }
764
761
  const collected = collectAssets(clientAssetsDir);
765
762
  clientAssets = collected.assets;
766
763
  fileList = collected.fileList;
764
+ if (plan.assets.excludeFile) {
765
+ delete clientAssets["/" + plan.assets.excludeFile];
766
+ delete clientAssets[plan.assets.excludeFile];
767
+ fileList = fileList.filter((p) => p !== plan.assets.excludeFile && p !== "/" + plan.assets.excludeFile);
768
+ }
767
769
  }
768
770
  section("Upload");
769
- if (isWorker) {
770
- consola.info(` Worker mode (${resolved.workerEntry})`);
771
- }
772
- else {
771
+ consola.info(` Mode: ${renderMode}${plan.worker.entry ? ` (worker: ${plan.worker.entry})` : ""}`);
772
+ if (plan.assets.enabled) {
773
773
  consola.info(` ${fileList.length} assets (${assetSummary(fileList)})`);
774
774
  }
775
775
  let serverFiles;
776
- if (isSSR && framework) {
777
- const serverEntry = getSSRServerEntry(framework);
778
- if (serverEntry) {
779
- const serverEntryPath = resolve(outputDir, serverEntry);
780
- if (existsSync(serverEntryPath)) {
781
- consola.start(" Bundling SSR server...");
782
- const bundled = await bundleSSRServer(serverEntryPath);
783
- serverFiles = { "server.js": Buffer.from(bundled).toString("base64") };
784
- consola.success(` SSR bundled (${Math.round(bundled.length / 1024)}KB)`);
776
+ switch (plan.worker.strategy) {
777
+ case "none":
778
+ break;
779
+ case "ssr-framework": {
780
+ // Sandbox path doesn't currently support pre-bundled SSR
781
+ // frameworks (Nuxt/SvelteKit/Astro CF); planDeploy only returns
782
+ // this strategy for SSR frameworks, which we still bundle via
783
+ // the legacy single-file path below to keep diff small.
784
+ const serverEntry = getSSRServerEntry(framework);
785
+ if (serverEntry) {
786
+ const serverEntryPath = resolve(outputDir, serverEntry);
787
+ if (existsSync(serverEntryPath)) {
788
+ consola.start(" Bundling SSR server...");
789
+ const bundled = await bundleSSRServer(serverEntryPath);
790
+ serverFiles = { "server.js": Buffer.from(bundled).toString("base64") };
791
+ consola.success(` SSR bundled (${Math.round(bundled.length / 1024)}KB)`);
792
+ }
785
793
  }
794
+ break;
786
795
  }
787
- }
788
- else if (isWorker && resolved?.workerEntry) {
789
- const workerEntryPath = resolve(cwd, resolved.workerEntry);
790
- if (!existsSync(workerEntryPath)) {
791
- consola.error(`Worker entry not found: ${resolved.workerEntry}`);
792
- process.exit(1);
796
+ case "esbuild-bundle": {
797
+ const workerEntryPath = resolve(cwd, plan.worker.entry);
798
+ consola.start(" Bundling worker...");
799
+ const bundled = await bundleWorker(workerEntryPath, cwd, {
800
+ hasClientAssets: plan.assets.enabled,
801
+ });
802
+ serverFiles = { "worker.js": Buffer.from(bundled).toString("base64") };
803
+ consola.success(` Worker bundled (${Math.round(bundled.length / 1024)}KB)`);
804
+ break;
805
+ }
806
+ case "upload-asis": {
807
+ const workerEntryPath = resolve(cwd, plan.worker.entry);
808
+ const bytes = readFileSync(workerEntryPath);
809
+ serverFiles = { "worker.js": bytes.toString("base64") };
810
+ consola.success(` Worker (pre-bundled, ${Math.round(bytes.length / 1024)}KB)`);
811
+ break;
793
812
  }
794
- consola.start(" Bundling worker...");
795
- const bundled = await bundleWorker(workerEntryPath, cwd, {
796
- hasClientAssets: false,
797
- });
798
- serverFiles = { "worker.js": Buffer.from(bundled).toString("base64") };
799
- consola.success(` Worker bundled (${Math.round(bundled.length / 1024)}KB)`);
800
813
  }
801
814
  // Deploy to sandbox
802
815
  if (!jsonMode) {
@@ -807,7 +820,7 @@ async function deploySandbox(cwd, skipBuild, jsonMode = false, resolved, tos) {
807
820
  const result = await sandboxDeploy({
808
821
  manifest: {
809
822
  assets: fileList,
810
- hasWorker: isSSR || isWorker,
823
+ hasWorker: plan.worker.strategy !== "none",
811
824
  entrypoint: resolved?.workerEntry ?? null,
812
825
  renderMode,
813
826
  },
@@ -987,11 +1000,34 @@ async function deployAuthenticated(cwd, resolved, token, skipBuild, jsonMode = f
987
1000
  if (!jsonMode)
988
1001
  consola.success(` Created project: ${project.slug}`);
989
1002
  }
990
- // Determine deploy mode
1003
+ // Determine deploy mode via the SDK's pure planner. The downstream
1004
+ // framework-specific branches (Astro CF, Next.js adapter, Nuxt, etc.)
1005
+ // are kept as-is — planDeploy decides the SHAPE (spa / ssr / worker
1006
+ // and whether the worker is source vs prebundled), and the legacy
1007
+ // collection logic decides WHERE to read the bytes from for each
1008
+ // framework. astroCF detection happens post-build below; for the
1009
+ // initial plan we conservatively pass null and the framework SSR
1010
+ // branch handles the adapter case.
991
1011
  const framework = resolved.framework;
992
1012
  const isSSR = isSSRFramework(framework);
993
- const isWorker = !framework && !!resolved.workerEntry;
994
- const renderMode = isWorker ? "worker" : (isSSR ? "ssr" : "spa");
1013
+ const initialPlan = planDeploy({
1014
+ framework,
1015
+ workerEntry: resolved.workerEntry ?? null,
1016
+ workerEntryExists: !!resolved.workerEntry &&
1017
+ existsSync(resolve(cwd, resolved.workerEntry)),
1018
+ buildOutput: resolved.buildOutput,
1019
+ buildOutputExists: existsSync(resolve(cwd, resolved.buildOutput)),
1020
+ astroCF: null,
1021
+ });
1022
+ if (!initialPlan.ok) {
1023
+ consola.error(initialPlan.reason);
1024
+ process.exit(1);
1025
+ }
1026
+ const isWorker = initialPlan.plan.worker.strategy === "esbuild-bundle" ||
1027
+ initialPlan.plan.worker.strategy === "upload-asis";
1028
+ const workerIsPrebundled = initialPlan.plan.worker.strategy === "upload-asis";
1029
+ const workerExcludeFromAssets = initialPlan.plan.assets.excludeFile;
1030
+ const renderMode = initialPlan.plan.renderMode;
995
1031
  // Detect Next.js mode for special build handling
996
1032
  const pkg = existsSync(join(cwd, "package.json"))
997
1033
  ? JSON.parse(readFileSync(join(cwd, "package.json"), "utf-8"))
@@ -1174,10 +1210,22 @@ async function deployAuthenticated(cwd, resolved, token, skipBuild, jsonMode = f
1174
1210
  }
1175
1211
  }
1176
1212
  else if (isWorker && resolved.workerEntry) {
1177
- // Worker: auto-generate _setEnv wrapper + esbuild bundle
1178
1213
  const workerEntryPath = resolve(cwd, resolved.workerEntry);
1179
- if (existsSync(workerEntryPath)) {
1180
- section("Bundle");
1214
+ if (!existsSync(workerEntryPath)) {
1215
+ consola.error(`Worker entry not found: ${resolved.workerEntry}`);
1216
+ process.exit(1);
1217
+ }
1218
+ section("Bundle");
1219
+ if (workerIsPrebundled) {
1220
+ // User's build script already produced a fully self-contained
1221
+ // bundle inside buildOutput; ship the bytes verbatim. No wrapper,
1222
+ // no Creek runtime injection — keeps the deployed Worker free of
1223
+ // any @solcreek/* dependency.
1224
+ const bytes = readFileSync(workerEntryPath);
1225
+ serverFiles = { "worker.js": bytes.toString("base64") };
1226
+ consola.success(` Worker (pre-bundled, ${Math.round(bytes.length / 1024)}KB)`);
1227
+ }
1228
+ else {
1181
1229
  consola.start(" Bundling worker...");
1182
1230
  const bundled = await bundleWorker(workerEntryPath, cwd, {
1183
1231
  hasClientAssets: !!workerHasClientAssets,
@@ -1187,10 +1235,14 @@ async function deployAuthenticated(cwd, resolved, token, skipBuild, jsonMode = f
1187
1235
  };
1188
1236
  consola.success(` Worker bundled (${Math.round(bundled.length / 1024)}KB)`);
1189
1237
  }
1190
- else {
1191
- consola.error(`Worker entry not found: ${resolved.workerEntry}`);
1192
- process.exit(1);
1193
- }
1238
+ }
1239
+ // When the worker bundle lives INSIDE the static asset dir
1240
+ // (e.g. dist/_worker.mjs), drop it from clientAssets so it isn't
1241
+ // double-uploaded as a publicly accessible file.
1242
+ if (workerExcludeFromAssets) {
1243
+ delete clientAssets[workerExcludeFromAssets];
1244
+ delete clientAssets["/" + workerExcludeFromAssets];
1245
+ fileList = fileList.filter((p) => p !== workerExcludeFromAssets && p !== "/" + workerExcludeFromAssets);
1194
1246
  }
1195
1247
  section("Deploy");
1196
1248
  consola.start(" Creating deployment...");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solcreek/cli",
3
- "version": "0.4.8",
3
+ "version": "0.4.9",
4
4
  "description": "CLI for the Creek deployment platform",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -33,7 +33,7 @@
33
33
  "esbuild": "^0.25.0",
34
34
  "smol-toml": "^1.3.1",
35
35
  "ws": "^8.20.0",
36
- "@solcreek/sdk": "0.4.3"
36
+ "@solcreek/sdk": "0.4.4"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@testing-library/dom": "^10.4.1",