create-shopnex 0.0.0-edge.542091c → 0.0.0-edge.c8ad7df

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.
Files changed (3) hide show
  1. package/README.md +23 -3
  2. package/dist/main.mjs +170 -60
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -63,6 +63,27 @@ its data in `<shop folder>/apps/`. The admin finds them by itself; install them
63
63
  your keys there. Stripe cannot reach a `localhost` address, so card payments need a public address for the app or
64
64
  `stripe listen`: see the Stripe app's settings page.
65
65
 
66
+ ## The shop's `.env`
67
+
68
+ The first run writes `<name>/.env` (readable by you only) with the two settings a shop needs, and every run reads it:
69
+
70
+ ```sh
71
+ DATABASE_URL=pglite:./.pglite # embedded database in the shop folder (a relative path is relative to it)
72
+ SHOPNEX_SECRET=<generated once> # encrypts the provider keys saved in the admin and signs sessions: keep it
73
+ ```
74
+
75
+ - **PostgreSQL:** set `DATABASE_URL=postgres://user:password@host:5432/shopnex` and start again. The migrations are applied
76
+ on start (`SHOPNEX_AUTO_MIGRATE`). The data in `.pglite/` is not moved (there is no tool for that yet).
77
+ - **Other settings** in the file (SMTP, and so on) are passed to the admin. The address, the ports and the build's own
78
+ files are set by the CLI and its flags and win over the file.
79
+ - Do not commit or share it. Without `SHOPNEX_SECRET` the saved provider keys cannot be read: a lost one means a new
80
+ shop.
81
+
82
+ The first run also writes a `.gitignore` (keeps `.env`, `.pglite/` and the apps' keys out of git) and the
83
+ `docker-compose.yml` (fetched from the repository, the file the [self-hosting docs](https://shopnex.ai/docs/self-hosting/docker) describe: ShopNex, the Stripe
84
+ app and PostgreSQL from prebuilt images), for when the shop should run in Docker instead. Existing files are never
85
+ overwritten.
86
+
66
87
  To start over, delete the shop folder.
67
88
 
68
89
  ## Not for production
@@ -78,9 +99,8 @@ migrations: JavaScript and WebAssembly, so one file serves every OS and CPU. `cr
78
99
  own version, so a version without a release says so. The checksum comes from the same place as the file: it catches a
79
100
  damaged download, not a tampered release.
80
101
 
81
- `SHOPNEX_SECRET` is generated once and kept in `<name>/data.secret` (readable by you only), so the same shop keeps its
82
- sessions and stored keys. `SHOPNEX_BUNDLE_URL` downloads from somewhere else (a mirror; the `.sha256` sits next to it)
83
- and `SHOPNEX_APP_DIR` changes where builds are unpacked.
102
+ `SHOPNEX_BUNDLE_URL` downloads from somewhere else (a mirror; the `.sha256` sits next to it) and `SHOPNEX_APP_DIR`
103
+ changes where builds are unpacked.
84
104
 
85
105
  **Edge:** `npx create-shopnex@edge` (or `pnpm create shopnex@edge`) runs the latest build of `main` (`.github/workflows/edge.yaml` replaces the file of
86
106
  the `edge` pre-release on every push). It downloads again only when `main` moved. It can be broken: use a release for
package/dist/main.mjs CHANGED
@@ -40,7 +40,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
40
40
  var define_PKG_default;
41
41
  var init_define_PKG = __esm({
42
42
  "<define:__PKG__>"() {
43
- define_PKG_default = { version: "0.0.0-edge.542091c", engines: { node: ">=22.22.0" } };
43
+ define_PKG_default = { version: "0.0.0-edge.c8ad7df", engines: { node: ">=22.22.0" } };
44
44
  }
45
45
  });
46
46
 
@@ -179,7 +179,7 @@ init_define_PKG();
179
179
 
180
180
  // src/cli.ts
181
181
  init_define_PKG();
182
- import { readFileSync as readFileSync3 } from "node:fs";
182
+ import { readFileSync as readFileSync4 } from "node:fs";
183
183
 
184
184
  // ../../node_modules/.pnpm/@clack+prompts@0.7.0/node_modules/@clack/prompts/dist/index.mjs
185
185
  init_define_PKG();
@@ -740,35 +740,9 @@ init_define_PKG();
740
740
 
741
741
  // src/plan.ts
742
742
  init_define_PKG();
743
- import { join as join2 } from "node:path";
744
-
745
- // src/paths.ts
746
- init_define_PKG();
747
- import { randomBytes } from "node:crypto";
748
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
749
- import { homedir } from "node:os";
750
- import { dirname, join, resolve } from "node:path";
751
- function resolveShopDir(name, cwd = process.cwd()) {
752
- const trimmed = name.trim();
753
- if (!trimmed) throw new UsageError("The shop name cannot be empty.");
754
- const expanded = trimmed === "~" ? homedir() : trimmed.startsWith("~/") ? join(homedir(), trimmed.slice(2)) : trimmed;
755
- return resolve(cwd, expanded);
756
- }
757
- var dataDirOf = (shopDir) => join(shopDir, "data");
758
- var databaseUrl = (dataDir) => `pglite:${dataDir}`;
759
- var appCacheDir = () => process.env.SHOPNEX_APP_DIR ?? join(homedir(), ".shopnex", "app");
760
- function ensureSecret(dataDir) {
761
- const file = `${dataDir}.secret`;
762
- if (!existsSync(file)) {
763
- mkdirSync(dirname(file), { recursive: true });
764
- writeFileSync(file, randomBytes(32).toString("hex"), { mode: 384 });
765
- }
766
- return readFileSync(file, "utf8").trim();
767
- }
768
-
769
- // src/plan.ts
743
+ import { join } from "node:path";
770
744
  function planFromBundle(input) {
771
- const { root, shopDir, dataDir, port, storefrontPort, secret, extras: extras2 } = input;
745
+ const { root, shopDir, databaseUrl, port, storefrontPort, secret, env, extras: extras2, appKeys = {} } = input;
772
746
  const extraUrl = (extra) => `http://localhost:${extra.port}`;
773
747
  const appUrl = `http://localhost:${port}`;
774
748
  return {
@@ -777,15 +751,19 @@ function planFromBundle(input) {
777
751
  processes: [
778
752
  {
779
753
  name: "admin",
780
- cwd: join2(root, "admin"),
754
+ cwd: join(root, "admin"),
781
755
  command: process.execPath,
782
756
  args: ["server.mjs"],
783
757
  env: {
758
+ // The shop's .env first: what the CLI decides (address, ports, the build's files) wins over it
759
+ ...env,
784
760
  NODE_ENV: "production",
785
761
  HOST: "127.0.0.1",
786
762
  PORT: String(port),
787
- DATABASE_URL: databaseUrl(dataDir),
788
- SHOPNEX_MIGRATIONS_DIR: join2(root, "migrations"),
763
+ DATABASE_URL: databaseUrl,
764
+ SHOPNEX_MIGRATIONS_DIR: join(root, "migrations"),
765
+ // PostgreSQL from the shop's .env is migrated at start, like the embedded database
766
+ SHOPNEX_AUTO_MIGRATE: "true",
789
767
  SHOPNEX_SECRET: secret,
790
768
  BETTER_AUTH_URL: appUrl,
791
769
  APP_URL: appUrl,
@@ -798,7 +776,7 @@ function planFromBundle(input) {
798
776
  },
799
777
  {
800
778
  name: "storefront",
801
- cwd: join2(root, "storefront"),
779
+ cwd: join(root, "storefront"),
802
780
  command: process.execPath,
803
781
  args: ["dist/server/entry.mjs"],
804
782
  env: {
@@ -813,7 +791,7 @@ function planFromBundle(input) {
813
791
  },
814
792
  ...extras2.map((extra) => ({
815
793
  name: extra.name,
816
- cwd: join2(root, "apps", extra.dir),
794
+ cwd: join(root, "apps", extra.dir),
817
795
  command: process.execPath,
818
796
  args: ["server.mjs"],
819
797
  env: {
@@ -822,8 +800,12 @@ function planFromBundle(input) {
822
800
  PORT: String(extra.port),
823
801
  [extra.urlEnv]: extraUrl(extra),
824
802
  SHOPNEX_INTERNAL_URL: appUrl,
825
- // Its identity key and the shops' settings: kept with the shop, so the same shop stays connected
826
- DATA_DIR: join2(shopDir, "apps", extra.dir)
803
+ // Its identity, kept with the shop so the same shop stays connected
804
+ [extra.keyEnv]: appKeys[extra.name] ?? "",
805
+ // Tables of its own: a schema in the shop's PostgreSQL, or its own embedded database
806
+ ...extra.database && {
807
+ APP_DATABASE_URL: databaseUrl.startsWith("pglite:") ? `pglite:${join(shopDir, "apps", extra.dir, ".pglite")}` : databaseUrl
808
+ }
827
809
  },
828
810
  readyUrl: `http://127.0.0.1:${extra.port}/shopnex/app.js`
829
811
  }))
@@ -860,12 +842,44 @@ async function pickApp(name, { interactive, list = apps }) {
860
842
  return list.find((app) => app.name === answer) ?? first;
861
843
  }
862
844
 
845
+ // src/app-keys.ts
846
+ init_define_PKG();
847
+ import { generateKeyPairSync } from "node:crypto";
848
+ import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
849
+ import { dirname, join as join2 } from "node:path";
850
+ var newAppKey = () => `snxk_${generateKeyPairSync("ed25519").privateKey.export({ type: "pkcs8", format: "der" }).toString("base64url")}`;
851
+ function loadAppKeys(shopDir, extras2, { write }) {
852
+ const keys = {};
853
+ for (const extra of extras2) {
854
+ const file = join2(shopDir, "apps", extra.dir, "app-key");
855
+ if (!existsSync(file)) {
856
+ if (!write) {
857
+ keys[extra.name] = "";
858
+ continue;
859
+ }
860
+ mkdirSync(dirname(file), { recursive: true });
861
+ writeFileSync(file, newAppKey(), { mode: 384, flag: "wx" });
862
+ }
863
+ chmodSync(file, 384);
864
+ keys[extra.name] = readFileSync(file, "utf8").trim();
865
+ }
866
+ return keys;
867
+ }
868
+
863
869
  // src/extras.ts
864
870
  init_define_PKG();
865
871
  var extras = [
866
- { name: "stripe", label: "Stripe", dir: "stripe", port: 4600, urlEnv: "STRIPE_APP_URL" },
867
- { name: "cj", label: "CJ Dropshipping", dir: "cj-dropshipping", port: 4700, urlEnv: "CJ_DROPSHIPPING_APP_URL" },
868
- { name: "shippo", label: "Shippo", dir: "shippo", port: 4900, urlEnv: "SHIPPO_APP_URL" }
872
+ { name: "stripe", label: "Stripe", dir: "stripe", port: 4600, urlEnv: "STRIPE_APP_URL", keyEnv: "STRIPE_APP_KEY" },
873
+ {
874
+ name: "cj",
875
+ label: "CJ Dropshipping",
876
+ dir: "cj-dropshipping",
877
+ port: 4700,
878
+ urlEnv: "CJ_DROPSHIPPING_APP_URL",
879
+ keyEnv: "CJ_DROPSHIPPING_APP_KEY",
880
+ database: true
881
+ },
882
+ { name: "shippo", label: "Shippo", dir: "shippo", port: 4900, urlEnv: "SHIPPO_APP_URL", keyEnv: "SHIPPO_APP_KEY" }
869
883
  ];
870
884
  function pickExtras(values, list = extras) {
871
885
  const names = (values ?? []).flatMap((value) => value.split(",")).map((name) => name.trim().toLowerCase());
@@ -892,15 +906,29 @@ import {
892
906
  renameSync,
893
907
  rmSync
894
908
  } from "node:fs";
895
- import { join as join3 } from "node:path";
909
+ import { join as join4 } from "node:path";
896
910
  import { spawnSync } from "node:child_process";
897
911
  import { Readable, Transform } from "node:stream";
898
912
  import { pipeline } from "node:stream/promises";
913
+
914
+ // src/paths.ts
915
+ init_define_PKG();
916
+ import { homedir } from "node:os";
917
+ import { join as join3, resolve } from "node:path";
918
+ function resolveShopDir(name, cwd = process.cwd()) {
919
+ const trimmed = name.trim();
920
+ if (!trimmed) throw new UsageError("The shop name cannot be empty.");
921
+ const expanded = trimmed === "~" ? homedir() : trimmed.startsWith("~/") ? join3(homedir(), trimmed.slice(2)) : trimmed;
922
+ return resolve(cwd, expanded);
923
+ }
924
+ var appCacheDir = () => process.env.SHOPNEX_APP_DIR ?? join3(homedir(), ".shopnex", "app");
925
+
926
+ // src/bundle.ts
899
927
  var isEdge = (version) => /-edge\./.test(version);
900
928
  var bundleName = (version) => `shopnex-v${version}-node.tgz`;
901
929
  var bundleUrl = (version) => process.env.SHOPNEX_BUNDLE_URL ?? (isEdge(version) ? "https://github.com/shopnex-ai/shopnex/releases/download/edge/shopnex-edge-node.tgz" : `https://github.com/shopnex-ai/shopnex/releases/download/v${version}/${bundleName(version)}`);
902
- var bundleDir = (key) => join3(appCacheDir(), key);
903
- var isInstalled = (key) => existsSync2(join3(bundleDir(key), "manifest.json"));
930
+ var bundleDir = (key) => join4(appCacheDir(), key);
931
+ var isInstalled = (key) => existsSync2(join4(bundleDir(key), "manifest.json"));
904
932
  var installedEdges = () => existsSync2(appCacheDir()) ? readdirSync(appCacheDir()).filter((name) => /^edge-[0-9a-f]{12}$/.test(name)) : [];
905
933
  async function get(url, what, version) {
906
934
  let response;
@@ -964,8 +992,8 @@ async function resolveBundle({ version }) {
964
992
  }
965
993
  const label = edge ? "the latest edge build" : `ShopNex ${version}`;
966
994
  say(`Downloading ${label}`);
967
- work = mkdtempSync(join3(appCacheDir(), `${key}.tmp-`));
968
- const file = join3(work, "bundle.tgz");
995
+ work = mkdtempSync(join4(appCacheDir(), `${key}.tmp-`));
996
+ const file = join4(work, "bundle.tgz");
969
997
  const actual = await download(
970
998
  await get(url, "ShopNex", version),
971
999
  file,
@@ -977,13 +1005,13 @@ async function resolveBundle({ version }) {
977
1005
  );
978
1006
  }
979
1007
  say("Unpacking");
980
- const unpacked = join3(work, "app");
1008
+ const unpacked = join4(work, "app");
981
1009
  mkdirSync2(unpacked);
982
1010
  const tar = spawnSync("tar", ["-xzf", file, "-C", unpacked], { encoding: "utf8" });
983
1011
  if (tar.error || tar.status !== 0) {
984
1012
  throw new UnavailableError(`Could not unpack the download with tar: ${tar.error?.message ?? tar.stderr.trim()}`);
985
1013
  }
986
- const manifestFile = join3(unpacked, "manifest.json");
1014
+ const manifestFile = join4(unpacked, "manifest.json");
987
1015
  const manifest = existsSync2(manifestFile) ? JSON.parse(readFileSync2(manifestFile, "utf8")) : void 0;
988
1016
  if (edge ? !manifest : manifest?.version !== version) {
989
1017
  throw new UnavailableError(
@@ -1016,6 +1044,81 @@ function assertNode(version, enginesRange) {
1016
1044
  }
1017
1045
  }
1018
1046
 
1047
+ // src/env.ts
1048
+ init_define_PKG();
1049
+ import { randomBytes } from "node:crypto";
1050
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, appendFileSync, writeFileSync as writeFileSync2 } from "node:fs";
1051
+ import { isAbsolute, join as join5, resolve as resolve2 } from "node:path";
1052
+ import { parseEnv } from "node:util";
1053
+ var DEFAULT_DATABASE_URL = "pglite:./.pglite";
1054
+ var template = (secret) => `# ShopNex: the settings of this shop. Edit, then start it again.
1055
+ #
1056
+ # Where the data lives. The default is an embedded database in ./.pglite (PGlite): one process owns it, for trying
1057
+ # ShopNex out. For a real database use PostgreSQL, for example:
1058
+ # DATABASE_URL=postgres://user:password@localhost:5432/shopnex
1059
+ # (docker-compose.yml runs ShopNex and PostgreSQL in Docker)
1060
+ DATABASE_URL=${DEFAULT_DATABASE_URL}
1061
+
1062
+ # Encrypts the provider keys saved in the admin and signs sessions. Keep it: a new one logs everyone out and makes the
1063
+ # saved keys unreadable. Do not share this file.
1064
+ SHOPNEX_SECRET=${secret}
1065
+ `;
1066
+ var resolveDatabaseUrl = (url, shopDir) => {
1067
+ if (!url.startsWith("pglite:")) return url;
1068
+ const path = url.slice("pglite:".length);
1069
+ return isAbsolute(path) ? url : `pglite:${resolve2(shopDir, path)}`;
1070
+ };
1071
+ var redactUrl = (url) => url.replace(/\/\/([^:@/]+):[^@/]*@/, "//$1:***@");
1072
+ function loadShopEnv(shopDir, { write }) {
1073
+ const file = join5(shopDir, ".env");
1074
+ const exists = existsSync3(file);
1075
+ const { DATABASE_URL, SHOPNEX_SECRET, ...rest } = exists ? parseEnv(readFileSync3(file, "utf8")) : {};
1076
+ const secret = SHOPNEX_SECRET?.trim() || (write ? randomBytes(32).toString("hex") : "");
1077
+ const databaseUrl = DATABASE_URL?.trim() || DEFAULT_DATABASE_URL;
1078
+ if (write) {
1079
+ if (!exists) {
1080
+ mkdirSync3(shopDir, { recursive: true });
1081
+ writeFileSync2(file, template(secret), { mode: 384 });
1082
+ } else {
1083
+ const missing = [
1084
+ DATABASE_URL?.trim() ? "" : `DATABASE_URL=${DEFAULT_DATABASE_URL}`,
1085
+ SHOPNEX_SECRET?.trim() ? "" : `SHOPNEX_SECRET=${secret}`
1086
+ ].filter(Boolean);
1087
+ if (missing.length)
1088
+ appendFileSync(file, `${readFileSync3(file, "utf8").endsWith("\n") ? "" : "\n"}${missing.join("\n")}
1089
+ `);
1090
+ }
1091
+ }
1092
+ return { databaseUrl: resolveDatabaseUrl(databaseUrl, shopDir), secret, rest, file };
1093
+ }
1094
+
1095
+ // src/scaffold.ts
1096
+ init_define_PKG();
1097
+ import { existsSync as existsSync4, mkdirSync as mkdirSync4, writeFileSync as writeFileSync3 } from "node:fs";
1098
+ import { join as join6 } from "node:path";
1099
+ var GITIGNORE = `# ShopNex: secrets and local data stay out of git
1100
+ .env
1101
+ .pglite/
1102
+ apps/*/.pglite/
1103
+ apps/*/app-key
1104
+ `;
1105
+ var composeUrl = (version) => `https://raw.githubusercontent.com/shopnex-ai/shopnex/${isEdge(version) ? "main" : `v${version}`}/deploy/docker-compose.yml`;
1106
+ async function writeScaffold(shopDir, version, fetcher = fetch) {
1107
+ mkdirSync4(shopDir, { recursive: true });
1108
+ const gitignore = join6(shopDir, ".gitignore");
1109
+ if (!existsSync4(gitignore)) writeFileSync3(gitignore, GITIGNORE);
1110
+ const compose = join6(shopDir, "docker-compose.yml");
1111
+ if (existsSync4(compose)) return;
1112
+ const url = composeUrl(version);
1113
+ try {
1114
+ const response = await fetcher(url, { signal: AbortSignal.timeout(5e3) });
1115
+ if (!response.ok) return `docker-compose.yml was not added: ${url} answered ${response.status}`;
1116
+ writeFileSync3(compose, await response.text());
1117
+ } catch (error) {
1118
+ return `docker-compose.yml was not added: ${url} could not be fetched (${error instanceof Error ? error.message : error})`;
1119
+ }
1120
+ }
1121
+
1019
1122
  // src/theme.ts
1020
1123
  init_define_PKG();
1021
1124
  var import_picocolors2 = __toESM(require_picocolors(), 1);
@@ -1030,14 +1133,14 @@ init_define_PKG();
1030
1133
  import { spawn } from "node:child_process";
1031
1134
  import { connect } from "node:net";
1032
1135
  var import_picocolors3 = __toESM(require_picocolors(), 1);
1033
- var sleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
1136
+ var sleep = (ms) => new Promise((resolve3) => setTimeout(resolve3, ms));
1034
1137
  function portInUse(port) {
1035
- const tryHost = (host) => new Promise((resolve2) => {
1138
+ const tryHost = (host) => new Promise((resolve3) => {
1036
1139
  const socket = connect({ port, host });
1037
1140
  socket.setTimeout(500);
1038
- socket.once("connect", () => (socket.destroy(), resolve2(true)));
1039
- socket.once("timeout", () => (socket.destroy(), resolve2(false)));
1040
- socket.once("error", () => resolve2(false));
1141
+ socket.once("connect", () => (socket.destroy(), resolve3(true)));
1142
+ socket.once("timeout", () => (socket.destroy(), resolve3(false)));
1143
+ socket.once("error", () => resolve3(false));
1041
1144
  });
1042
1145
  return Promise.all([tryHost("127.0.0.1"), tryHost("::1")]).then((used) => used.some(Boolean));
1043
1146
  }
@@ -1063,7 +1166,7 @@ function openBrowser(url) {
1063
1166
  child.unref();
1064
1167
  }
1065
1168
  function runPlan(plan, { open, verbose }) {
1066
- return new Promise((resolve2) => {
1169
+ return new Promise((resolve3) => {
1067
1170
  const children = [];
1068
1171
  const tails = /* @__PURE__ */ new Map();
1069
1172
  let stopping = false;
@@ -1081,7 +1184,7 @@ function runPlan(plan, { open, verbose }) {
1081
1184
  } catch {
1082
1185
  }
1083
1186
  }
1084
- setTimeout(() => resolve2(code), 300);
1187
+ setTimeout(() => resolve3(code), 300);
1085
1188
  };
1086
1189
  for (const [index, proc] of plan.processes.entries()) {
1087
1190
  const child = spawn(proc.command, proc.args, {
@@ -1144,13 +1247,14 @@ function runPlan(plan, { open, verbose }) {
1144
1247
  }
1145
1248
 
1146
1249
  // src/cli.ts
1147
- var pkg = typeof define_PKG_default !== "undefined" ? define_PKG_default : JSON.parse(readFileSync3(new URL("../package.json", import.meta.url), "utf8"));
1250
+ var pkg = typeof define_PKG_default !== "undefined" ? define_PKG_default : JSON.parse(readFileSync4(new URL("../package.json", import.meta.url), "utf8"));
1148
1251
  var HELP = `create-shopnex ${pkg.version}: try ShopNex on this machine with one command.
1149
1252
 
1150
1253
  Usage
1151
1254
  create-shopnex [name] [options] (name defaults to shopnex)
1152
1255
 
1153
- Creates the folder <name> (shopnex when you leave it out) and keeps the shop's database in it.
1256
+ Creates the folder <name> (shopnex when you leave it out) with the shop's settings (.env) and, by default, its
1257
+ database. Change DATABASE_URL in the .env to use PostgreSQL.
1154
1258
  Starts the admin (with the API and checkout) and the storefront on an embedded database. Nothing else to install.
1155
1259
  This is for trying ShopNex out: one process owns the data. For a real shop use Docker or PostgreSQL.
1156
1260
 
@@ -1167,6 +1271,7 @@ Options
1167
1271
  `;
1168
1272
  function describe({
1169
1273
  shopDir,
1274
+ database,
1170
1275
  plan,
1171
1276
  source,
1172
1277
  extras: extras2 = []
@@ -1174,6 +1279,7 @@ function describe({
1174
1279
  const rows = [
1175
1280
  ["Source", source],
1176
1281
  ["Folder", shopDir],
1282
+ ["Database", redactUrl(database)],
1177
1283
  ["Admin", `${plan.appUrl}/admin`],
1178
1284
  ["Storefront", plan.storefrontUrl],
1179
1285
  ...extras2.map((extra) => [extra.label, `http://localhost:${extra.port} (install it from the admin)`])
@@ -1191,8 +1297,10 @@ async function run(argv) {
1191
1297
  const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
1192
1298
  const name = options2.name ?? DEFAULT_SHOP_NAME;
1193
1299
  const shopDir = resolveShopDir(name);
1194
- const dataDir = dataDirOf(shopDir);
1195
1300
  const extras2 = pickExtras(options2.with);
1301
+ const shopEnv = loadShopEnv(shopDir, { write: !options2.dryRun });
1302
+ const scaffoldWarning = options2.dryRun ? void 0 : await writeScaffold(shopDir, pkg.version);
1303
+ if (scaffoldWarning) f2.warn(scaffoldWarning);
1196
1304
  const ports = [
1197
1305
  { port: options2.port, flag: "--port" },
1198
1306
  { port: options2.storefrontPort, flag: "--storefront-port" },
@@ -1206,12 +1314,14 @@ async function run(argv) {
1206
1314
  root,
1207
1315
  shopDir,
1208
1316
  extras: extras2,
1209
- dataDir,
1317
+ databaseUrl: shopEnv.databaseUrl,
1318
+ env: shopEnv.rest,
1210
1319
  port: options2.port,
1211
1320
  storefrontPort: options2.storefrontPort,
1212
- secret: options2.dryRun ? "" : ensureSecret(dataDir)
1321
+ secret: shopEnv.secret,
1322
+ appKeys: loadAppKeys(shopDir, extras2, { write: !options2.dryRun })
1213
1323
  });
1214
- le(describe({ shopDir, plan, source, extras: extras2 }), app.label);
1324
+ le(describe({ shopDir, database: shopEnv.databaseUrl, plan, source, extras: extras2 }), app.label);
1215
1325
  if (options2.dryRun) return $e("Dry run: nothing was started.");
1216
1326
  f2.warn("This is a trial database: it is not for production.");
1217
1327
  process.exitCode = await runPlan(plan, { open: options2.open, verbose: options2.verbose });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-shopnex",
3
- "version": "0.0.0-edge.542091c",
3
+ "version": "0.0.0-edge.c8ad7df",
4
4
  "description": "Try ShopNex with one command: npx create-shopnex starts the admin and the storefront on an embedded database, nothing else to install",
5
5
  "license": "MIT",
6
6
  "homepage": "https://shopnex.ai",