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.
- package/README.md +23 -3
- package/dist/main.mjs +170 -60
- 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
|
-
`
|
|
82
|
-
|
|
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.
|
|
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
|
|
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
|
|
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,
|
|
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:
|
|
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
|
|
788
|
-
SHOPNEX_MIGRATIONS_DIR:
|
|
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:
|
|
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:
|
|
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
|
|
826
|
-
|
|
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
|
-
{
|
|
868
|
-
|
|
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
|
|
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) =>
|
|
903
|
-
var isInstalled = (key) => existsSync2(
|
|
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(
|
|
968
|
-
const file =
|
|
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 =
|
|
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 =
|
|
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((
|
|
1136
|
+
var sleep = (ms) => new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
1034
1137
|
function portInUse(port) {
|
|
1035
|
-
const tryHost = (host) => new Promise((
|
|
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(),
|
|
1039
|
-
socket.once("timeout", () => (socket.destroy(),
|
|
1040
|
-
socket.once("error", () =>
|
|
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((
|
|
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(() =>
|
|
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(
|
|
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)
|
|
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
|
-
|
|
1317
|
+
databaseUrl: shopEnv.databaseUrl,
|
|
1318
|
+
env: shopEnv.rest,
|
|
1210
1319
|
port: options2.port,
|
|
1211
1320
|
storefrontPort: options2.storefrontPort,
|
|
1212
|
-
secret:
|
|
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.
|
|
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",
|