@selvajs/cli 4.8.0-beta.8 → 4.8.0

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/src/paths.js CHANGED
@@ -21,9 +21,52 @@ export function requiredNodeRange() {
21
21
  }
22
22
  }
23
23
 
24
- // A deployment directory must contain an .env and an ecosystem.config.cjs.
25
- // Anything else (selva.config.js, node_modules) is "should be there" but the
26
- // CLI doesn't require it — `selva init` is allowed to fix a partial install.
24
+ /**
25
+ * This CLI's own version, written into `.selva-version` so a later `selva
26
+ * migrate` can tell which scaffold layout it is looking at.
27
+ *
28
+ * Read from package.json for the same reason as `requiredNodeRange`: a literal
29
+ * in code stops tracking the release the moment someone forgets to bump it, and
30
+ * the marker's whole job is to be accurate about which CLI wrote the directory.
31
+ */
32
+ export function scaffoldVersion() {
33
+ try {
34
+ const here = dirname(fileURLToPath(import.meta.url));
35
+ const pkg = JSON.parse(readFileSync(join(here, '..', 'package.json'), 'utf8'));
36
+ return typeof pkg.version === 'string' ? pkg.version : '0.0.0';
37
+ } catch {
38
+ return '0.0.0';
39
+ }
40
+ }
41
+
42
+ /**
43
+ * A scaffolding template shipped inside the installed runtime. The CLI cannot
44
+ * import from `@selvajs/selva` — it is what installs it — so it reads the files
45
+ * out of node_modules after install.
46
+ */
47
+ export function runtimeTemplatePath(dir, name) {
48
+ return join(dir, 'node_modules', '@selvajs', 'selva', 'templates', name);
49
+ }
50
+
51
+ /**
52
+ * The `.env` template to merge operator values into: the runtime's `.env.example`
53
+ * when installed, otherwise the deployment's own `.env` so its comments and
54
+ * ordering survive a rewrite.
55
+ *
56
+ * Returns `''` when neither exists rather than throwing — `mergeEnv` appends
57
+ * every value to an empty template, so the operator gets a usable file instead
58
+ * of an ENOENT from a command that was only asked to change one key.
59
+ */
60
+ export function readEnvTemplate(dir) {
61
+ const template = runtimeTemplatePath(dir, '.env.example');
62
+ if (existsSync(template)) return readFileSync(template, 'utf8');
63
+ const env = join(dir, '.env');
64
+ if (existsSync(env)) return readFileSync(env, 'utf8');
65
+ return '';
66
+ }
67
+
68
+ // Either .env or ecosystem.config.cjs is enough — selva.config.js and
69
+ // node_modules are expected too, but init should still fix a partial install.
27
70
  export function isDeploymentDir(dir) {
28
71
  return existsSync(join(dir, '.env')) || existsSync(join(dir, 'ecosystem.config.cjs'));
29
72
  }
package/src/prompts.js CHANGED
@@ -18,9 +18,7 @@ export function collectConfigFromEnv(env = process.env) {
18
18
  'local',
19
19
  'SELVA_AUTH_PROVIDER'
20
20
  );
21
- // Default data/storage to auth — matches the prompt's "use same provider
22
- // for all three" default. Header-auth has no data layer, so it falls
23
- // through to local unless explicitly overridden.
21
+ // Header-auth has no data layer, so it falls through to local unless overridden.
24
22
  const dataDefault = auth === 'header' ? 'local' : auth;
25
23
  const data = pick(
26
24
  env.SELVA_DATA_PROVIDER,
@@ -72,7 +70,6 @@ export function collectConfigFromEnv(env = process.env) {
72
70
  values.HEADER_AUTH_DISPLAY_NAME_HEADER = env.HEADER_AUTH_DISPLAY_NAME_HEADER;
73
71
  }
74
72
 
75
- // ── Bootstrap admin ──────────────────────────────────────────────────
76
73
  const adminRequired = auth === 'header' || tenancy === 'multi';
77
74
  const adminEmail = env.BOOTSTRAP_INSTANCE_ADMIN_EMAIL || '';
78
75
  if (adminRequired && !adminEmail) {
@@ -127,15 +124,11 @@ function requireEnv(env, name) {
127
124
  return v;
128
125
  }
129
126
 
130
- // Run full interactive prompt sequence; return env vars. Caller decides merge vs fresh.
131
- // `defaults` pre-populates prompts (cheap re-run).
132
127
  export async function collectConfig({ defaults = {}, mode = 'create' } = {}) {
133
128
  const isInit = mode === 'init';
134
129
 
135
130
  p.intro(pc.bgCyan(pc.black(isInit ? ' selva init ' : ' Selva — new deployment ')));
136
131
 
137
- // Brand prompts (SELVA_BRAND_NAME / COPYRIGHT_NAME / TAGLINE / DESCRIPTION)
138
-
139
132
  const tenancy = await p.select({
140
133
  message: 'Tenancy mode',
141
134
  initialValue: defaults.SELVA_TENANCY ?? 'single',
@@ -169,7 +162,6 @@ export async function collectConfig({ defaults = {}, mode = 'create' } = {}) {
169
162
  });
170
163
  cancelOn(auth);
171
164
 
172
- // Header-auth: auth-only; user must pick separate data/storage backend.
173
165
  let data;
174
166
  let storage;
175
167
  if (auth === 'header') {
@@ -197,7 +189,6 @@ export async function collectConfig({ defaults = {}, mode = 'create' } = {}) {
197
189
  cancelOn(storageChoice);
198
190
  storage = storageChoice;
199
191
  } else {
200
- // Operators usually want same backend for all three; ask once, default the others.
201
192
  const mixProviders = await p.confirm({
202
193
  message: `Use ${pc.cyan(auth)} for data and storage too?`,
203
194
  initialValue: pickSameProviderDefault(defaults, auth)
@@ -281,7 +272,6 @@ export async function collectConfig({ defaults = {}, mode = 'create' } = {}) {
281
272
  }
282
273
 
283
274
  if (auth === 'header') {
284
- // HEADER_AUTH_DATA_DIR: ask only if data provider isn't local (fallback available).
285
275
  if (data !== 'local') {
286
276
  const dataDir = await p.text({
287
277
  message: 'HEADER_AUTH_DATA_DIR — directory for header-allowlist.json',
@@ -330,7 +320,7 @@ export async function collectConfig({ defaults = {}, mode = 'create' } = {}) {
330
320
  providerValues.HEADER_AUTH_DISPLAY_NAME_HEADER = stringValue(display);
331
321
  }
332
322
 
333
- // Header-auth: default to loopback (not enforced; Docker networks may override).
323
+ // Not enforced — a Docker network can still route to the container on another host.
334
324
  const bindLoopback = await p.confirm({
335
325
  message: 'Bind the app to 127.0.0.1 only? (recommended for header-auth)',
336
326
  initialValue: !defaults.HOST || defaults.HOST === '127.0.0.1' || defaults.HOST === 'localhost'
@@ -339,7 +329,6 @@ export async function collectConfig({ defaults = {}, mode = 'create' } = {}) {
339
329
  providerValues.HOST = bindLoopback ? '127.0.0.1' : stringValue(defaults.HOST);
340
330
  }
341
331
 
342
- // Bootstrap admin (checked only while no admin exists; safe to leave set).
343
332
  if (auth === 'header') {
344
333
  p.note(
345
334
  [
@@ -494,10 +483,11 @@ export async function collectConfig({ defaults = {}, mode = 'create' } = {}) {
494
483
  return values;
495
484
  }
496
485
 
497
- // Init: default to "same provider" only if current .env already shows that.
498
486
  function pickSameProviderDefault(defaults, auth) {
499
487
  const data = defaults.SELVA_DATA_PROVIDER ?? auth;
500
488
  const storage = defaults.SELVA_STORAGE_PROVIDER ?? auth;
489
+ // No existing defaults means a fresh `create`, not a re-prompted `init` — default to
490
+ // "same provider for all three" since there's nothing yet to disagree with auth.
501
491
  if (!defaults.SELVA_DATA_PROVIDER && !defaults.SELVA_STORAGE_PROVIDER) return true;
502
492
  return data === auth && storage === auth;
503
493
  }
@@ -527,5 +517,3 @@ function headerAuthSecurityWarning() {
527
517
  pc.dim('See packages/providers/header-auth/README.md for the full deployment checklist.')
528
518
  ].join('\n');
529
519
  }
530
-
531
- export { p as prompts, pc as colors };
package/bin/create.js DELETED
@@ -1,7 +0,0 @@
1
- #!/usr/bin/env node
2
- import { runCreate } from '../src/commands/create.js';
3
-
4
- runCreate(process.argv.slice(2)).catch((err) => {
5
- console.error(err instanceof Error ? err.message : err);
6
- process.exit(1);
7
- });