@karmaniverous/smoz 0.2.13 → 0.2.15

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/dist/cjs/index.js CHANGED
@@ -867,14 +867,13 @@ function wrapHandler(functionConfig, business, opts) {
867
867
  assertKeysSubset(envConfig.stage.paramsSchema, envConfig.stage.envKeys, 'stage.envKeys');
868
868
  return async (event, context) => {
869
869
  // Compose typed env schema and parse process.env
870
- const all = deriveAllKeys(envConfig.global.envKeys, envConfig.stage.envKeys, (functionConfig.fnEnvKeys ?? []));
870
+ const all = deriveAllKeys(envConfig.global.envKeys, envConfig.stage.envKeys, functionConfig.fnEnvKeys ?? []);
871
871
  const { globalPick, stagePick } = splitKeysBySchema(all, envConfig.global.paramsSchema, envConfig.stage.paramsSchema);
872
872
  const envSchema = buildEnvSchema(globalPick, stagePick, envConfig.global.paramsSchema, envConfig.stage.paramsSchema);
873
873
  const env = parseTypedEnv(envSchema, process.env);
874
874
  const logger = console;
875
875
  // Non-HTTP: call business directly
876
- const httpTokens = opts?.httpEventTypeTokens ??
877
- defaultHttpEventTypeTokens;
876
+ const httpTokens = opts?.httpEventTypeTokens ?? defaultHttpEventTypeTokens;
878
877
  const isHttp = httpTokens.includes(functionConfig.eventType);
879
878
  if (!isHttp) {
880
879
  return business(event, context, { env, logger });
@@ -937,9 +936,7 @@ const createRegistry = (deps) => {
937
936
  ...(options.basePath ? { basePath: options.basePath } : {}),
938
937
  ...(options.httpContexts ? { httpContexts: options.httpContexts } : {}),
939
938
  ...(options.contentType ? { contentType: options.contentType } : {}),
940
- ...(mergedFnEnvKeys.length
941
- ? { fnEnvKeys: mergedFnEnvKeys }
942
- : {}),
939
+ ...(mergedFnEnvKeys.length ? { fnEnvKeys: mergedFnEnvKeys } : {}),
943
940
  ...(options.eventSchema ? { eventSchema: options.eventSchema } : {}),
944
941
  ...(options.responseSchema
945
942
  ? { responseSchema: options.responseSchema }
@@ -956,9 +953,7 @@ const createRegistry = (deps) => {
956
953
  ...(options.basePath ? { basePath: options.basePath } : {}),
957
954
  ...(options.httpContexts ? { httpContexts: options.httpContexts } : {}),
958
955
  ...(options.contentType ? { contentType: options.contentType } : {}),
959
- ...(mergedFnEnvKeys.length
960
- ? { fnEnvKeys: mergedFnEnvKeys }
961
- : {}),
956
+ ...(mergedFnEnvKeys.length ? { fnEnvKeys: mergedFnEnvKeys } : {}),
962
957
  ...(options.eventSchema ? { eventSchema: options.eventSchema } : {}),
963
958
  ...(options.responseSchema
964
959
  ? { responseSchema: options.responseSchema }
@@ -1125,7 +1120,7 @@ const buildAllServerlessFunctions = (registry, serverless, buildFnEnv) => {
1125
1120
  .split(path.sep)
1126
1121
  .join('/');
1127
1122
  const handler = `${handlerFileRel}.${serverless.defaultHandlerFileExport}`;
1128
- let events = [];
1123
+ let events;
1129
1124
  try {
1130
1125
  const { method, basePath, contexts } = resolveHttpFromFunctionConfig({
1131
1126
  functionName: r.functionName,
@@ -1,19 +1,17 @@
1
1
  #!/usr/bin/env node
2
- 'use strict';
2
+ import { createCli } from '@karmaniverous/get-dotenv/cli';
3
+ import { awsPlugin, cmdPlugin, batchPlugin } from '@karmaniverous/get-dotenv/plugins';
4
+ import { definePlugin } from '@karmaniverous/get-dotenv/cliHost';
5
+ import { packageDirectorySync } from 'package-directory';
6
+ import fs, { existsSync, promises, readFileSync } from 'node:fs';
7
+ import path, { join, posix, sep, dirname, relative, resolve } from 'node:path';
8
+ import chokidar from 'chokidar';
9
+ import { spawn, spawnSync } from 'node:child_process';
10
+ import { buildSpawnEnv } from '@karmaniverous/get-dotenv';
11
+ import { pathToFileURL, fileURLToPath } from 'node:url';
12
+ import { stdout, stdin } from 'node:process';
13
+ import { createInterface } from 'node:readline/promises';
3
14
 
4
- var cliHost = require('@karmaniverous/get-dotenv/cliHost');
5
- var plugins = require('@karmaniverous/get-dotenv/plugins');
6
- var packageDirectory = require('package-directory');
7
- var fs = require('node:fs');
8
- var path = require('node:path');
9
- var chokidar = require('chokidar');
10
- var node_child_process = require('node:child_process');
11
- var getDotenv = require('@karmaniverous/get-dotenv');
12
- var node_url = require('node:url');
13
- var node_process = require('node:process');
14
- var promises = require('node:readline/promises');
15
-
16
- var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
17
15
  /**
18
16
  * SMOZ CLI: add
19
17
  *
@@ -38,7 +36,7 @@ const HTTP_METHODS = new Set([
38
36
  'options',
39
37
  'trace',
40
38
  ]);
41
- const toPosix$2 = (p) => p.split(path.sep).join('/');
39
+ const toPosix$2 = (p) => p.split(sep).join('/');
42
40
  const formatMaybe$1 = async (root, filePath, source) => {
43
41
  try {
44
42
  const prettier = (await import('prettier'));
@@ -50,10 +48,10 @@ const formatMaybe$1 = async (root, filePath, source) => {
50
48
  }
51
49
  };
52
50
  const writeIfAbsent$1 = async (outFile, content) => {
53
- if (fs.existsSync(outFile))
51
+ if (existsSync(outFile))
54
52
  return { created: false };
55
- await fs.promises.mkdir(path.dirname(outFile), { recursive: true });
56
- await fs.promises.writeFile(outFile, content, 'utf8');
53
+ await promises.mkdir(dirname(outFile), { recursive: true });
54
+ await promises.writeFile(outFile, content, 'utf8');
57
55
  return { created: true };
58
56
  };
59
57
  /** True if a segment encodes a path param (:id | {id} | [id]). */
@@ -201,10 +199,10 @@ const runAdd = async (root, spec) => {
201
199
  // Derive path template and param names for OpenAPI hints
202
200
  const paramNames = baseParts.filter(isParamSeg).map(getParamName);
203
201
  const pathTemplate = '/' + pathSegs.join('/');
204
- const dir = path.join(root, 'app', 'functions', token, ...dirSegs, ...(method ? [method] : []));
205
- const lambdaPath = path.join(dir, 'lambda.ts');
206
- const handlerPath = path.join(dir, 'handler.ts');
207
- const openapiPath = path.join(dir, 'openapi.ts');
202
+ const dir = join(root, 'app', 'functions', token, ...dirSegs, ...(method ? [method] : []));
203
+ const lambdaPath = join(dir, 'lambda.ts');
204
+ const handlerPath = join(dir, 'handler.ts');
205
+ const openapiPath = join(dir, 'openapi.ts');
208
206
  // Compute contents
209
207
  const files = [];
210
208
  if (isHttp) {
@@ -249,9 +247,9 @@ const runAdd = async (root, spec) => {
249
247
  const formatted = await formatMaybe$1(root, f.path, f.content);
250
248
  const { created: c } = await writeIfAbsent$1(f.path, formatted);
251
249
  if (c)
252
- created.push(path.posix.normalize(f.path));
250
+ created.push(posix.normalize(f.path));
253
251
  else
254
- skipped.push(path.posix.normalize(f.path));
252
+ skipped.push(posix.normalize(f.path));
255
253
  }
256
254
  return { created, skipped };
257
255
  };
@@ -306,7 +304,7 @@ const launchOffline = async (root, opts) => {
306
304
  const spawnOffline = async (root, cmd, verbose) => {
307
305
  // Normalize child environment via get-dotenv when available; safe fallback otherwise.
308
306
  const baseEnv = { ...process.env };
309
- const childEnv = getDotenv.buildSpawnEnv(baseEnv);
307
+ const childEnv = buildSpawnEnv(baseEnv);
310
308
  // Optional diagnostics to verify the child sees sane temp-related envs
311
309
  if (verbose) {
312
310
  const snap = {
@@ -319,7 +317,7 @@ const spawnOffline = async (root, cmd, verbose) => {
319
317
  };
320
318
  process.stdout.write(`[offline] env snapshot: ${JSON.stringify(snap)}\n`);
321
319
  }
322
- const child = node_child_process.spawn(cmd.cmd, cmd.args, {
320
+ const child = spawn(cmd.cmd, cmd.args, {
323
321
  cwd: root,
324
322
  shell: cmd.shell,
325
323
  stdio: ['ignore', 'pipe', 'pipe'],
@@ -363,7 +361,7 @@ const spawnOffline = async (root, cmd, verbose) => {
363
361
  const findTsxCli = (root) => {
364
362
  // Prefer invoking the JS entry to avoid shell .cmd quirks on Windows.
365
363
  const js = path.resolve(root, 'node_modules', 'tsx', 'dist', 'cli.js');
366
- if (fs.existsSync(js)) {
364
+ if (existsSync(js)) {
367
365
  return {
368
366
  cmd: process.execPath,
369
367
  args: [js, 'app/config/openapi.ts'],
@@ -376,7 +374,7 @@ const findTsxCli = (root) => {
376
374
  };
377
375
  const findPrettierCli = (root) => {
378
376
  const js = path.resolve(root, 'node_modules', 'prettier', 'bin', 'prettier.cjs');
379
- if (!fs.existsSync(js)) {
377
+ if (!existsSync(js)) {
380
378
  throw new Error(`prettier not found at ${js}. Install it as a devDependency.`);
381
379
  }
382
380
  return {
@@ -390,21 +388,21 @@ const runOpenapi = async (root, opts) => {
390
388
  const outFile = path.resolve(root, 'app', 'generated', 'openapi.json');
391
389
  let before;
392
390
  try {
393
- if (fs.existsSync(outFile))
394
- before = fs.readFileSync(outFile, 'utf8');
391
+ if (existsSync(outFile))
392
+ before = readFileSync(outFile, 'utf8');
395
393
  }
396
394
  catch {
397
395
  // ignore read errors; treat as absent
398
396
  before = undefined;
399
397
  }
400
398
  // Build a normalized child env via get-dotenv (static import).
401
- const env = getDotenv.buildSpawnEnv({});
399
+ const env = buildSpawnEnv({});
402
400
  // Step 1: generate the OpenAPI spec.
403
401
  const tsx = findTsxCli(root);
404
402
  if (opts?.verbose) {
405
403
  console.log(`[openapi] ${[tsx.cmd, ...tsx.args].join(' ')}`);
406
404
  }
407
- const genRes = node_child_process.spawnSync(tsx.cmd, tsx.args, {
405
+ const genRes = spawnSync(tsx.cmd, tsx.args, {
408
406
  cwd: root,
409
407
  stdio: 'inherit',
410
408
  shell: tsx.shell,
@@ -424,7 +422,7 @@ const runOpenapi = async (root, opts) => {
424
422
  if (opts?.verbose) {
425
423
  console.log(`[openapi] ${[prettier.cmd, ...prettier.args].join(' ')}`);
426
424
  }
427
- const fmtRes = node_child_process.spawnSync(prettier.cmd, prettier.args, {
425
+ const fmtRes = spawnSync(prettier.cmd, prettier.args, {
428
426
  cwd: root,
429
427
  stdio: 'inherit',
430
428
  shell: prettier.shell,
@@ -441,9 +439,9 @@ const runOpenapi = async (root, opts) => {
441
439
  }
442
440
  // Determine whether the file content changed
443
441
  try {
444
- if (!fs.existsSync(outFile))
442
+ if (!existsSync(outFile))
445
443
  return before !== undefined; // deleted vs existed
446
- const after = fs.readFileSync(outFile, 'utf8');
444
+ const after = readFileSync(outFile, 'utf8');
447
445
  return before === undefined ? true : after !== before;
448
446
  }
449
447
  catch {
@@ -463,7 +461,7 @@ const runOpenapi = async (root, opts) => {
463
461
  *
464
462
  * Idempotent and formatted (uses Prettier if available).
465
463
  */
466
- const toPosix$1 = (p) => p.split(path.sep).join('/');
464
+ const toPosix$1 = (p) => p.split(sep).join('/');
467
465
  const withoutTsExt = (p) => p.endsWith('.ts') ? p.slice(0, -3) : p;
468
466
  const HEADER = `/* AUTO-GENERATED by smoz register — DO NOT EDIT */
469
467
  `;
@@ -480,21 +478,21 @@ const formatMaybe = async (root, filePath, source) => {
480
478
  };
481
479
  const writeIfChanged = async (outFile, content) => {
482
480
  try {
483
- const prev = await fs.promises.readFile(outFile, 'utf8');
481
+ const prev = await promises.readFile(outFile, 'utf8');
484
482
  if (prev === content)
485
483
  return false;
486
484
  }
487
485
  catch {
488
486
  // file missing — proceed
489
487
  }
490
- await fs.promises.mkdir(path.dirname(outFile), { recursive: true });
491
- await fs.promises.writeFile(outFile, content, 'utf8');
488
+ await promises.mkdir(dirname(outFile), { recursive: true });
489
+ await promises.writeFile(outFile, content, 'utf8');
492
490
  return true;
493
491
  };
494
492
  const walk$1 = async (dir, out = []) => {
495
- const entries = await fs.promises.readdir(dir, { withFileTypes: true });
493
+ const entries = await promises.readdir(dir, { withFileTypes: true });
496
494
  for (const ent of entries) {
497
- const p = path.join(dir, ent.name);
495
+ const p = join(dir, ent.name);
498
496
  if (ent.isDirectory()) {
499
497
  await walk$1(p, out);
500
498
  }
@@ -505,13 +503,13 @@ const walk$1 = async (dir, out = []) => {
505
503
  return out;
506
504
  };
507
505
  const collect = async (root) => {
508
- const base = path.join(root, 'app', 'functions');
506
+ const base = join(root, 'app', 'functions');
509
507
  const buckets = { lambda: [], openapi: [], serverless: [] };
510
- if (!fs.existsSync(base))
508
+ if (!existsSync(base))
511
509
  return buckets;
512
510
  const files = await walk$1(base);
513
511
  for (const abs of files) {
514
- const name = abs.split(path.sep).pop() ?? '';
512
+ const name = abs.split(sep).pop() ?? '';
515
513
  if (name === 'lambda.ts')
516
514
  buckets.lambda.push(abs);
517
515
  if (name === 'openapi.ts')
@@ -531,7 +529,7 @@ const collect = async (root) => {
531
529
  * dynamic import in the inline server works reliably under tsx.
532
530
  */
533
531
  const makeImports = (root, outDir, files) => files.map((abs) => {
534
- const relFromOut = toPosix$1(path.relative(outDir, abs));
532
+ const relFromOut = toPosix$1(relative(outDir, abs));
535
533
  const noExt = withoutTsExt(relFromOut);
536
534
  const spec = noExt.startsWith('.') ? noExt : `./${noExt}`;
537
535
  return `import '${spec}';`;
@@ -543,23 +541,23 @@ const buildFile = (imports) => {
543
541
  };
544
542
  const runRegister = async (root) => {
545
543
  const buckets = await collect(root);
546
- const outDir = path.join(root, 'app', 'generated');
544
+ const outDir = join(root, 'app', 'generated');
547
545
  const lambdaImports = makeImports(root, outDir, buckets.lambda);
548
546
  const openapiImports = makeImports(root, outDir, buckets.openapi);
549
547
  const serverlessImports = makeImports(root, outDir, buckets.serverless);
550
- const fnsPath = path.join(outDir, 'register.functions.ts');
551
- const oaiPath = path.join(outDir, 'register.openapi.ts');
552
- const srvPath = path.join(outDir, 'register.serverless.ts');
548
+ const fnsPath = join(outDir, 'register.functions.ts');
549
+ const oaiPath = join(outDir, 'register.openapi.ts');
550
+ const srvPath = join(outDir, 'register.serverless.ts');
553
551
  const fns = await formatMaybe(root, fnsPath, buildFile(lambdaImports));
554
552
  const oai = await formatMaybe(root, oaiPath, buildFile(openapiImports));
555
553
  const srv = await formatMaybe(root, srvPath, buildFile(serverlessImports));
556
554
  const wrote = [];
557
555
  if (await writeIfChanged(fnsPath, fns))
558
- wrote.push(path.posix.normalize(fnsPath));
556
+ wrote.push(posix.normalize(fnsPath));
559
557
  if (await writeIfChanged(oaiPath, oai))
560
- wrote.push(path.posix.normalize(oaiPath));
558
+ wrote.push(posix.normalize(oaiPath));
561
559
  if (await writeIfChanged(srvPath, srv))
562
- wrote.push(path.posix.normalize(srvPath));
560
+ wrote.push(posix.normalize(srvPath));
563
561
  return { wrote };
564
562
  };
565
563
 
@@ -591,8 +589,8 @@ const resolveStage = async (root, cliStage, verbose) => {
591
589
  // getdotenv.config.json → plugins.smoz.stage
592
590
  try {
593
591
  const cfgPath = path.resolve(root, 'getdotenv.config.json');
594
- if (fs.existsSync(cfgPath)) {
595
- const raw = fs.readFileSync(cfgPath, 'utf8');
592
+ if (existsSync(cfgPath)) {
593
+ const raw = readFileSync(cfgPath, 'utf8');
596
594
  const parsed = JSON.parse(raw);
597
595
  const fromCfg = parsed.plugins?.smoz?.stage;
598
596
  if (typeof fromCfg === 'string' && fromCfg.trim().length > 0) {
@@ -617,7 +615,7 @@ const seedEnvForStage = async (root, stage, verbose) => {
617
615
  // Best effort: import the app config to read declared env keys and concrete values.
618
616
  // Preserve existing process.env values; only seed when unset.
619
617
  try {
620
- const appConfigUrl = node_url.pathToFileURL(path.resolve(root, 'app', 'config', 'app.config.ts')).href;
618
+ const appConfigUrl = pathToFileURL(path.resolve(root, 'app', 'config', 'app.config.ts')).href;
621
619
  // Dynamically import the TS module under tsx
622
620
  const mod = (await import(appConfigUrl));
623
621
  const app = mod.app;
@@ -691,7 +689,7 @@ const resolveTsxCommand = (root, tsEntry) => {
691
689
  shell: false,
692
690
  };
693
691
  }
694
- const probe = node_child_process.spawnSync(process.platform === 'win32' ? 'tsx.cmd' : 'tsx', ['--version'], { shell: true });
692
+ const probe = spawnSync(process.platform === 'win32' ? 'tsx.cmd' : 'tsx', ['--version'], { shell: true });
695
693
  if (typeof probe.status === 'number' && probe.status === 0) {
696
694
  return {
697
695
  cmd: process.platform === 'win32' ? 'tsx.cmd' : 'tsx',
@@ -713,13 +711,13 @@ const launchInline = async (root, opts) => {
713
711
  // Prefer __dirname when available (CJS), otherwise derive from import.meta.url (ESM).
714
712
  const here = typeof __dirname === 'string'
715
713
  ? __dirname
716
- : path.dirname(node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))));
717
- const pkgRoot = packageDirectory.packageDirectorySync({ cwd: here }) ?? process.cwd();
714
+ : path.dirname(fileURLToPath(import.meta.url));
715
+ const pkgRoot = packageDirectorySync({ cwd: here }) ?? process.cwd();
718
716
  const { entry } = resolveInlineEntry(pkgRoot);
719
717
  const makeTsx = () => {
720
718
  const { cmd, args, shell } = resolveTsxCommand(root, entry);
721
719
  return (async () => {
722
- const env = getDotenv.buildSpawnEnv({
720
+ const env = buildSpawnEnv({
723
721
  ...process.env,
724
722
  // Enable tsconfig paths for "@/..." during TS fallback.
725
723
  TSX_TSCONFIG_PATHS: '1',
@@ -727,7 +725,7 @@ const launchInline = async (root, opts) => {
727
725
  SMOZ_PORT: String(opts.port),
728
726
  SMOZ_VERBOSE: opts.verbose ? '1' : '',
729
727
  });
730
- return node_child_process.spawn(cmd, args, {
728
+ return spawn(cmd, args, {
731
729
  cwd: root,
732
730
  stdio: 'inherit',
733
731
  shell,
@@ -893,16 +891,16 @@ const runDev = async (root, opts) => {
893
891
  };
894
892
 
895
893
  const writeIfAbsent = async (outFile, content) => {
896
- if (fs.existsSync(outFile))
894
+ if (existsSync(outFile))
897
895
  return { created: false };
898
- await fs.promises.mkdir(path.dirname(outFile), { recursive: true });
899
- await fs.promises.writeFile(outFile, content, 'utf8');
896
+ await promises.mkdir(dirname(outFile), { recursive: true });
897
+ await promises.writeFile(outFile, content, 'utf8');
900
898
  return { created: true };
901
899
  };
902
900
  const walk = async (dir, out = []) => {
903
- const entries = await fs.promises.readdir(dir, { withFileTypes: true });
901
+ const entries = await promises.readdir(dir, { withFileTypes: true });
904
902
  for (const ent of entries) {
905
- const p = path.join(dir, ent.name);
903
+ const p = join(dir, ent.name);
906
904
  if (ent.isDirectory()) {
907
905
  await walk(p, out);
908
906
  }
@@ -914,7 +912,7 @@ const walk = async (dir, out = []) => {
914
912
  };
915
913
  const readJson = async (file) => {
916
914
  try {
917
- const data = await fs.promises.readFile(file, 'utf8');
915
+ const data = await promises.readFile(file, 'utf8');
918
916
  return JSON.parse(data);
919
917
  }
920
918
  catch {
@@ -922,8 +920,8 @@ const readJson = async (file) => {
922
920
  }
923
921
  };
924
922
  const writeJson = async (file, obj) => {
925
- await fs.promises.mkdir(path.dirname(file), { recursive: true });
926
- await fs.promises.writeFile(file, JSON.stringify(obj, null, 2), 'utf8');
923
+ await promises.mkdir(dirname(file), { recursive: true });
924
+ await promises.writeFile(file, JSON.stringify(obj, null, 2), 'utf8');
927
925
  };
928
926
 
929
927
  const askConflict = async (rl, filePath) => {
@@ -949,25 +947,25 @@ const copyDirWithConflicts = async (srcDir, dstRoot, created, skipped, examples,
949
947
  const files = await walk(srcDir);
950
948
  let sticky;
951
949
  for (const abs of files) {
952
- const rel = path.relative(srcDir, abs);
950
+ const rel = relative(srcDir, abs);
953
951
  if (opts.exclude && opts.exclude(rel)) {
954
952
  // Skip dev-only or excluded files silently.
955
953
  continue;
956
954
  }
957
- const dest = path.resolve(dstRoot, rel);
958
- const data = await fs.promises.readFile(abs, 'utf8');
959
- if (!fs.existsSync(dest)) {
955
+ const dest = resolve(dstRoot, rel);
956
+ const data = await promises.readFile(abs, 'utf8');
957
+ if (!existsSync(dest)) {
960
958
  const { created: c } = await writeIfAbsent(dest, data);
961
959
  if (c)
962
- created.push(path.posix.normalize(dest));
960
+ created.push(posix.normalize(dest));
963
961
  else
964
- skipped.push(path.posix.normalize(dest));
962
+ skipped.push(posix.normalize(dest));
965
963
  continue;
966
964
  }
967
965
  // Conflict flow
968
966
  let decision = opts.conflict === 'ask' ? 'example' : opts.conflict;
969
967
  if (opts.conflict === 'ask' && opts.rl && !sticky) {
970
- const ans = await askConflict(opts.rl, path.posix.normalize(dest));
968
+ const ans = await askConflict(opts.rl, posix.normalize(dest));
971
969
  if (ans === 'all-overwrite') {
972
970
  sticky = 'overwrite';
973
971
  }
@@ -984,32 +982,32 @@ const copyDirWithConflicts = async (srcDir, dstRoot, created, skipped, examples,
984
982
  if (sticky)
985
983
  decision = sticky;
986
984
  if (decision === 'overwrite') {
987
- await fs.promises.mkdir(path.dirname(dest), { recursive: true });
988
- await fs.promises.writeFile(dest, data, 'utf8');
989
- created.push(path.posix.normalize(dest));
985
+ await promises.mkdir(dirname(dest), { recursive: true });
986
+ await promises.writeFile(dest, data, 'utf8');
987
+ created.push(posix.normalize(dest));
990
988
  }
991
989
  else if (decision === 'example') {
992
990
  const ex = `${dest}.example`;
993
991
  const { created: c } = await writeIfAbsent(ex, data);
994
992
  if (c)
995
- examples.push(path.posix.normalize(ex));
993
+ examples.push(posix.normalize(ex));
996
994
  else
997
- skipped.push(path.posix.normalize(ex));
995
+ skipped.push(posix.normalize(ex));
998
996
  }
999
997
  else {
1000
- skipped.push(path.posix.normalize(dest));
998
+ skipped.push(posix.normalize(dest));
1001
999
  }
1002
1000
  }
1003
1001
  };
1004
1002
 
1005
1003
  const detectPm = (root) => {
1006
- if (fs.existsSync(path.join(root, 'pnpm-lock.yaml')))
1004
+ if (existsSync(join(root, 'pnpm-lock.yaml')))
1007
1005
  return 'pnpm';
1008
- if (fs.existsSync(path.join(root, 'yarn.lock')))
1006
+ if (existsSync(join(root, 'yarn.lock')))
1009
1007
  return 'yarn';
1010
- if (fs.existsSync(path.join(root, 'package-lock.json')))
1008
+ if (existsSync(join(root, 'package-lock.json')))
1011
1009
  return 'npm';
1012
- if (fs.existsSync(path.join(root, 'bun.lockb')))
1010
+ if (existsSync(join(root, 'bun.lockb')))
1013
1011
  return 'bun';
1014
1012
  const ua = process.env.npm_config_user_agent ?? '';
1015
1013
  if (ua.includes('pnpm'))
@@ -1028,7 +1026,7 @@ const runInstall = (root, pm) => {
1028
1026
  const known = pm === 'pnpm' || pm === 'yarn' || pm === 'bun' || pm === 'npm';
1029
1027
  if (!known)
1030
1028
  return 'unknown-pm';
1031
- const res = node_child_process.spawnSync(pm, ['install'], {
1029
+ const res = spawnSync(pm, ['install'], {
1032
1030
  stdio: 'inherit',
1033
1031
  cwd: root,
1034
1032
  shell: true,
@@ -1093,8 +1091,8 @@ const mergeAdditive = (target, source) => {
1093
1091
  */
1094
1092
  const ensureToolkitDependency = async (targetPkg, templatesBase) => {
1095
1093
  try {
1096
- const toolkitRoot = path.dirname(templatesBase);
1097
- const toolkitPkg = await readJson(path.join(toolkitRoot, 'package.json'));
1094
+ const toolkitRoot = dirname(templatesBase);
1095
+ const toolkitPkg = await readJson(join(toolkitRoot, 'package.json'));
1098
1096
  const verRaw = toolkitPkg?.version?.trim();
1099
1097
  const depVersion = verRaw ? `^${verRaw}` : '^0.0.0';
1100
1098
  const deps = targetPkg.dependencies ?? {};
@@ -1117,27 +1115,27 @@ const ensureToolkitDependency = async (targetPkg, templatesBase) => {
1117
1115
  * a consuming app and from this repository.
1118
1116
  + */
1119
1117
  const resolveTemplatesBase = () => {
1120
- const here = path.dirname(node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))));
1121
- const pkgRoot = packageDirectory.packageDirectorySync({ cwd: here }) ?? process.cwd();
1122
- return path.resolve(pkgRoot, 'templates');
1118
+ const here = dirname(fileURLToPath(import.meta.url));
1119
+ const pkgRoot = packageDirectorySync({ cwd: here }) ?? process.cwd();
1120
+ return resolve(pkgRoot, 'templates');
1123
1121
  };
1124
1122
  const toPosix = (p) =>
1125
1123
  // local helper for path presentation (not used for FS operations)
1126
1124
  p.replace(/\\/g, '/');
1127
1125
 
1128
1126
  const seedRegisterPlaceholders = async (root) => {
1129
- const genDir = path.join(root, 'app', 'generated');
1127
+ const genDir = join(root, 'app', 'generated');
1130
1128
  const seeds = [
1131
1129
  {
1132
- path: path.join(genDir, 'register.functions.ts'),
1130
+ path: join(genDir, 'register.functions.ts'),
1133
1131
  content: '/* AUTO-GENERATED placeholder; will be rewritten by `smoz register` */\nexport {};\n',
1134
1132
  },
1135
1133
  {
1136
- path: path.join(genDir, 'register.openapi.ts'),
1134
+ path: join(genDir, 'register.openapi.ts'),
1137
1135
  content: '/* AUTO-GENERATED placeholder; will be rewritten by `smoz register` */\nexport {};\n',
1138
1136
  },
1139
1137
  {
1140
- path: path.join(genDir, 'register.serverless.ts'),
1138
+ path: join(genDir, 'register.serverless.ts'),
1141
1139
  content: '/* AUTO-GENERATED placeholder; will be rewritten by `smoz register` */\nexport {};\n',
1142
1140
  },
1143
1141
  ];
@@ -1145,12 +1143,12 @@ const seedRegisterPlaceholders = async (root) => {
1145
1143
  const skipped = [];
1146
1144
  for (const s of seeds) {
1147
1145
  const { created: c } = await writeIfAbsent(s.path, s.content);
1148
- (c ? created : skipped).push(path.posix.normalize(s.path));
1146
+ (c ? created : skipped).push(posix.normalize(s.path));
1149
1147
  }
1150
1148
  return { created, skipped };
1151
1149
  };
1152
1150
 
1153
- const toPosixSep = (p) => p.split(path.sep).join('/');
1151
+ const toPosixSep = (p) => p.split(sep).join('/');
1154
1152
  // Exclude dev-only type stubs (not meant for downstream apps).
1155
1153
  const excludeDev = (rel) => {
1156
1154
  const posixRel = rel.replace(/\\/g, '/');
@@ -1177,19 +1175,19 @@ const runInit = async (root, template = 'default', opts) => {
1177
1175
  const optAll = opts ?? {};
1178
1176
  const templatesBase = resolveTemplatesBase();
1179
1177
  // Resolve template source: named template or filesystem path
1180
- const templateIsPath = fs.existsSync(template) && (await fs.promises.stat(template)).isDirectory();
1178
+ const templateIsPath = existsSync(template) && (await promises.stat(template)).isDirectory();
1181
1179
  const srcBase = templateIsPath
1182
- ? path.resolve(template)
1183
- : path.resolve(templatesBase, template);
1184
- const projectBase = path.resolve(templatesBase, 'project');
1185
- if (!fs.existsSync(srcBase)) {
1180
+ ? resolve(template)
1181
+ : resolve(templatesBase, template);
1182
+ const projectBase = resolve(templatesBase, 'project');
1183
+ if (!existsSync(srcBase)) {
1186
1184
  throw new Error(`Template "${template}" not found (path or name). Tried: ${toPosix(srcBase)}.`);
1187
1185
  }
1188
1186
  // 1) Copy shared project boilerplate
1189
- if (fs.existsSync(projectBase)) {
1187
+ if (existsSync(projectBase)) {
1190
1188
  const rl = optAll.yes === true
1191
1189
  ? undefined
1192
- : promises.createInterface({ input: node_process.stdin, output: node_process.stdout, terminal: true });
1190
+ : createInterface({ input: stdin, output: stdout, terminal: true });
1193
1191
  let policy;
1194
1192
  const c = optAll.conflict;
1195
1193
  if (c === 'overwrite' || c === 'example' || c === 'skip' || c === 'ask')
@@ -1209,7 +1207,7 @@ const runInit = async (root, template = 'default', opts) => {
1209
1207
  {
1210
1208
  const rl = optAll.yes === true
1211
1209
  ? undefined
1212
- : promises.createInterface({ input: node_process.stdin, output: node_process.stdout, terminal: true });
1210
+ : createInterface({ input: stdin, output: stdout, terminal: true });
1213
1211
  let policy;
1214
1212
  const c = optAll.conflict;
1215
1213
  if (c === 'overwrite' || c === 'example' || c === 'skip' || c === 'ask')
@@ -1227,21 +1225,21 @@ const runInit = async (root, template = 'default', opts) => {
1227
1225
  }
1228
1226
  // 2.5) Convert template 'gitignore' into a real '.gitignore'
1229
1227
  try {
1230
- const giSrc = path.join(root, 'gitignore');
1231
- const giDot = path.join(root, '.gitignore');
1232
- if (fs.existsSync(giSrc)) {
1233
- if (!fs.existsSync(giDot)) {
1234
- await fs.promises.rename(giSrc, giDot);
1235
- created.push(path.posix.normalize(giDot));
1228
+ const giSrc = join(root, 'gitignore');
1229
+ const giDot = join(root, '.gitignore');
1230
+ if (existsSync(giSrc)) {
1231
+ if (!existsSync(giDot)) {
1232
+ await promises.rename(giSrc, giDot);
1233
+ created.push(posix.normalize(giDot));
1236
1234
  }
1237
1235
  else {
1238
- const example = path.join(root, 'gitignore.example');
1239
- if (!fs.existsSync(example)) {
1240
- const data = await fs.promises.readFile(giSrc, 'utf8');
1241
- await fs.promises.writeFile(example, data, 'utf8');
1242
- examples.push(path.posix.normalize(example));
1236
+ const example = join(root, 'gitignore.example');
1237
+ if (!existsSync(example)) {
1238
+ const data = await promises.readFile(giSrc, 'utf8');
1239
+ await promises.writeFile(example, data, 'utf8');
1240
+ examples.push(posix.normalize(example));
1243
1241
  }
1244
- await fs.promises.rm(giSrc, { force: true });
1242
+ await promises.rm(giSrc, { force: true });
1245
1243
  }
1246
1244
  }
1247
1245
  }
@@ -1250,22 +1248,22 @@ const runInit = async (root, template = 'default', opts) => {
1250
1248
  }
1251
1249
  // 2.6) Convert 'tsconfig.downstream.json' into 'tsconfig.json'
1252
1250
  try {
1253
- const ds = path.join(root, 'tsconfig.downstream.json');
1254
- const dst = path.join(root, 'tsconfig.json');
1255
- if (fs.existsSync(ds)) {
1256
- if (!fs.existsSync(dst)) {
1257
- await fs.promises.rename(ds, dst);
1258
- created.push(path.posix.normalize(dst));
1251
+ const ds = join(root, 'tsconfig.downstream.json');
1252
+ const dst = join(root, 'tsconfig.json');
1253
+ if (existsSync(ds)) {
1254
+ if (!existsSync(dst)) {
1255
+ await promises.rename(ds, dst);
1256
+ created.push(posix.normalize(dst));
1259
1257
  }
1260
1258
  else {
1261
1259
  // If a tsconfig already exists, write an example and remove the source
1262
- const example = path.join(root, 'tsconfig.json.example');
1263
- if (!fs.existsSync(example)) {
1264
- const data = await fs.promises.readFile(ds, 'utf8');
1265
- await fs.promises.writeFile(example, data, 'utf8');
1266
- examples.push(path.posix.normalize(example));
1260
+ const example = join(root, 'tsconfig.json.example');
1261
+ if (!existsSync(example)) {
1262
+ const data = await promises.readFile(ds, 'utf8');
1263
+ await promises.writeFile(example, data, 'utf8');
1264
+ examples.push(posix.normalize(example));
1267
1265
  }
1268
- await fs.promises.rm(ds, { force: true });
1266
+ await promises.rm(ds, { force: true });
1269
1267
  }
1270
1268
  }
1271
1269
  }
@@ -1279,7 +1277,7 @@ const runInit = async (root, template = 'default', opts) => {
1279
1277
  skipped.push(...res.skipped);
1280
1278
  }
1281
1279
  // 3) package.json presence (create when missing)
1282
- const pkgPath = path.join(root, 'package.json');
1280
+ const pkgPath = join(root, 'package.json');
1283
1281
  let pkg = await readJson(pkgPath);
1284
1282
  if (!pkg) {
1285
1283
  const name = toPosixSep(root).split('/').pop() ?? 'smoz-app';
@@ -1292,13 +1290,13 @@ const runInit = async (root, template = 'default', opts) => {
1292
1290
  };
1293
1291
  if (!optAll.dryRun)
1294
1292
  await writeJson(pkgPath, pkg);
1295
- created.push(path.posix.normalize(pkgPath));
1293
+ created.push(posix.normalize(pkgPath));
1296
1294
  }
1297
1295
  // 4) Merge manifest additively (prefer template's embedded package.json)
1298
- const templatePkgPath = path.resolve(srcBase, 'package.json');
1299
- const manifest = fs.existsSync(templatePkgPath)
1296
+ const templatePkgPath = resolve(srcBase, 'package.json');
1297
+ const manifest = existsSync(templatePkgPath)
1300
1298
  ? await readJson(templatePkgPath)
1301
- : await readJson(path.resolve(templatesBase, '.manifests', `package.${template}.json`));
1299
+ : await readJson(resolve(templatesBase, '.manifests', `package.${template}.json`));
1302
1300
  let pkgChanged = false;
1303
1301
  if (manifest) {
1304
1302
  const added = mergeAdditive(pkg, manifest);
@@ -1339,139 +1337,136 @@ const runInit = async (root, template = 'default', opts) => {
1339
1337
  * Registers init/add/register/openapi/dev on a GetDotenvCli instance and
1340
1338
  * delegates to existing implementations. No business logic here.
1341
1339
  */
1342
- const repoRoot = () => packageDirectory.packageDirectorySync() ?? process.cwd();
1343
- const smozPlugin = () => cliHost.definePlugin({
1344
- id: 'smoz',
1345
- setup(cli) {
1346
- // init
1347
- cli
1348
- .command('init')
1349
- .description('Scaffold a new SMOZ app from a template')
1350
- .option('-t, --template <name>', 'template name', 'default')
1351
- .option('-y, --yes', 'assume “example” on conflicts')
1352
- .option('--no-install', 'do not install dependencies')
1353
- .option('--install <pm>', 'explicit package manager (npm|pnpm|yarn|bun)')
1354
- .option('--conflict <policy>', 'overwrite|example|skip|ask')
1355
- .action(async (opts) => {
1356
- const root = repoRoot();
1357
- const template = typeof opts.template === 'string' ? opts.template : 'default';
1358
- const yes = Boolean(opts.yes);
1359
- const noInstall = opts.install === undefined &&
1360
- opts['noInstall'] !== undefined
1361
- ? Boolean(opts['noInstall'])
1362
- : Boolean(opts['noInstall']);
1363
- const pmRaw = opts.install ?? undefined;
1364
- const conflict = opts.conflict ?? undefined;
1365
- await runInit(root, template, {
1366
- ...(yes ? { yes } : {}),
1367
- ...(noInstall ? { noInstall } : {}),
1368
- ...(pmRaw ? { install: pmRaw } : {}),
1369
- ...(conflict ? { conflict } : {}),
1370
- });
1371
- });
1372
- // add
1373
- cli
1374
- .command('add')
1375
- .description('Scaffold a function under app/functions')
1376
- .argument('<spec>', 'spec: <eventType>/<segments...>/<method>')
1377
- .action(async (spec) => {
1378
- const root = repoRoot();
1379
- if (typeof spec !== 'string' || !spec.trim()) {
1380
- throw new Error('smoz add: missing <spec> (e.g., rest/foo/get)');
1381
- }
1382
- await runAdd(root, spec);
1383
- });
1384
- // register
1385
- cli
1386
- .command('register')
1387
- .description('Generate side-effect register files')
1388
- .action(async () => {
1389
- const root = repoRoot();
1390
- await runRegister(root);
1391
- });
1392
- // openapi
1393
- cli
1394
- .command('openapi')
1395
- .description('Generate OpenAPI document (app/generated/openapi.json)')
1396
- .option('-V, --verbose', 'verbose output')
1397
- .action(async (opts) => {
1398
- const root = repoRoot();
1399
- await runOpenapi(root, { verbose: !!opts.verbose });
1340
+ const repoRoot = () => packageDirectorySync() ?? process.cwd();
1341
+ const setupSmozCommands = (cli) => {
1342
+ // init
1343
+ cli
1344
+ .command('init')
1345
+ .description('Scaffold a new SMOZ app from a template')
1346
+ .option('-t, --template <name>', 'template name', 'default')
1347
+ .option('-y, --yes', 'assume “example” on conflicts')
1348
+ .option('--no-install', 'do not install dependencies')
1349
+ .option('--install <pm>', 'explicit package manager (npm|pnpm|yarn|bun)')
1350
+ .option('--conflict <policy>', 'overwrite|example|skip|ask')
1351
+ .action(async (opts) => {
1352
+ const root = repoRoot();
1353
+ const template = typeof opts.template === 'string' ? opts.template : 'default';
1354
+ const yes = Boolean(opts.yes);
1355
+ const noInstall = Boolean(opts['noInstall']);
1356
+ const pmRaw = opts.install ?? undefined;
1357
+ const conflict = opts.conflict ?? undefined;
1358
+ await runInit(root, template, {
1359
+ ...(yes ? { yes } : {}),
1360
+ ...(noInstall ? { noInstall } : {}),
1361
+ ...(pmRaw ? { install: pmRaw } : {}),
1362
+ ...(conflict ? { conflict } : {}),
1400
1363
  });
1401
- // dev
1402
- cli
1403
- .command('dev')
1404
- .description('Dev loop: register/openapi + local backend (inline|offline)')
1405
- .option('-r, --register', 'run register step (default on)')
1406
- .option('-R, --no-register', 'disable register step')
1407
- .option('-o, --openapi', 'run openapi step (default on)')
1408
- .option('-O, --no-openapi', 'disable openapi step')
1409
- .option('-l, --local [mode]', 'local mode: inline | offline | false (default inline)')
1410
- .option('-s, --stage <name>', 'stage name')
1411
- .option('-p, --port <number>', 'port (0 = random free port)')
1412
- .option('-V, --verbose', 'verbose output')
1413
- .action(async (opts) => {
1414
- const root = repoRoot();
1415
- // register/openapi defaults (on); explicit negations take precedence.
1416
- const register = opts.register === true
1417
- ? true
1418
- : opts.noRegister
1419
- ? false
1420
- : true;
1421
- const openapi = opts.openapi === true
1422
- ? true
1423
- : opts.noOpenapi
1424
- ? false
1425
- : true;
1426
- // local mode parsing
1427
- const raw = opts.local;
1428
- let local = 'inline';
1429
- if (raw === 'inline' || raw === 'offline')
1430
- local = raw;
1431
- else if (raw === true)
1432
- local = 'inline';
1433
- else if (raw === 'false')
1434
- local = false;
1435
- const port = typeof opts.port === 'string'
1436
- ? Number(opts.port)
1437
- : typeof opts.port === 'number'
1438
- ? opts.port
1439
- : undefined;
1440
- await runDev(root, {
1441
- register,
1442
- openapi,
1443
- local,
1444
- ...(typeof opts.stage === 'string' ? { stage: opts.stage } : {}),
1445
- ...(typeof port === 'number' ? { port } : {}),
1446
- verbose: !!opts.verbose,
1447
- });
1364
+ });
1365
+ // add
1366
+ cli
1367
+ .command('add')
1368
+ .description('Scaffold a function under app/functions')
1369
+ .argument('<spec>', 'spec: <eventType>/<segments...>/<method>')
1370
+ .action(async (spec) => {
1371
+ const root = repoRoot();
1372
+ if (typeof spec !== 'string' || !spec.trim()) {
1373
+ throw new Error('smoz add: missing <spec> (e.g., rest/foo/get)');
1374
+ }
1375
+ await runAdd(root, spec);
1376
+ });
1377
+ // register
1378
+ cli
1379
+ .command('register')
1380
+ .description('Generate side-effect register files')
1381
+ .action(async () => {
1382
+ const root = repoRoot();
1383
+ await runRegister(root);
1384
+ });
1385
+ // openapi
1386
+ cli
1387
+ .command('openapi')
1388
+ .description('Generate OpenAPI document (app/generated/openapi.json)')
1389
+ .option('-V, --verbose', 'verbose output')
1390
+ .action(async (opts) => {
1391
+ const root = repoRoot();
1392
+ await runOpenapi(root, { verbose: !!opts.verbose });
1393
+ });
1394
+ // dev
1395
+ cli
1396
+ .command('dev')
1397
+ .description('Dev loop: register/openapi + local backend (inline|offline)')
1398
+ .option('-r, --register', 'run register step (default on)')
1399
+ .option('-R, --no-register', 'disable register step')
1400
+ .option('-o, --openapi', 'run openapi step (default on)')
1401
+ .option('-O, --no-openapi', 'disable openapi step')
1402
+ .option('-l, --local [mode]', 'local mode: inline | offline | false (default inline)')
1403
+ .option('-s, --stage <name>', 'stage name')
1404
+ .option('-p, --port <number>', 'port (0 = random free port)')
1405
+ .option('-V, --verbose', 'verbose output')
1406
+ .action(async (opts) => {
1407
+ const root = repoRoot();
1408
+ // register/openapi defaults (on); explicit negations take precedence.
1409
+ const register = opts.register === true
1410
+ ? true
1411
+ : opts.noRegister
1412
+ ? false
1413
+ : true;
1414
+ const openapi = opts.openapi === true
1415
+ ? true
1416
+ : opts.noOpenapi
1417
+ ? false
1418
+ : true;
1419
+ // local mode parsing
1420
+ const raw = opts.local;
1421
+ let local = 'inline';
1422
+ if (raw === 'inline' || raw === 'offline')
1423
+ local = raw;
1424
+ else if (raw === true)
1425
+ local = 'inline';
1426
+ else if (raw === 'false')
1427
+ local = false;
1428
+ const port = typeof opts.port === 'string'
1429
+ ? Number(opts.port)
1430
+ : typeof opts.port === 'number'
1431
+ ? opts.port
1432
+ : undefined;
1433
+ await runDev(root, {
1434
+ register,
1435
+ openapi,
1436
+ local,
1437
+ ...(typeof opts.stage === 'string' ? { stage: opts.stage } : {}),
1438
+ ...(typeof port === 'number' ? { port } : {}),
1439
+ verbose: !!opts.verbose,
1448
1440
  });
1449
- },
1441
+ });
1442
+ };
1443
+ const smozPlugin = () => definePlugin({
1444
+ ns: 'smoz',
1445
+ setup: setupSmozCommands,
1450
1446
  });
1451
1447
 
1452
1448
  /**
1453
1449
  * SMOZ CLI — plugin-first host built on get-dotenv.
1454
1450
  *
1455
- * - Build a GetDotenvCli host, install included plugins (cmd/batch/aws),
1456
- * and install the SMOZ command plugin (init/add/register/openapi/dev).
1457
- * - Resolve dotenv context once, then parse argv.
1451
+ * - Create the supported GetDotenv CLI runner so root options, resolution hooks,
1452
+ * and plugin installation stay aligned with the get-dotenv host API.
1453
+ * - Install included plugins (cmd/batch/aws) and the SMOZ command plugin
1454
+ * (init/add/register/openapi/dev).
1458
1455
  */
1459
- const main = async () => {
1460
- const cli = new cliHost.GetDotenvCli('smoz');
1461
- await cli.brand({
1462
- importMetaUrl: (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)),
1463
- description: 'SMOZ CLI',
1464
- });
1465
- cli
1466
- .attachRootOptions()
1467
- .use(smozPlugin())
1468
- .use(plugins.awsPlugin())
1469
- .use(plugins.cmdPlugin({
1470
- asDefault: true,
1471
- optionAlias: '-c, --cmd <command...>',
1472
- }))
1473
- .use(plugins.batchPlugin())
1474
- .passOptions();
1475
- await cli.parseAsync();
1476
- };
1456
+ const main = createCli({
1457
+ alias: 'smoz',
1458
+ compose: (cli) => {
1459
+ const setupResult = smozPlugin().setup(cli);
1460
+ if (setupResult !== undefined) {
1461
+ throw new Error('smozPlugin root setup must remain synchronous.');
1462
+ }
1463
+ return cli
1464
+ .use(awsPlugin())
1465
+ .use(cmdPlugin({
1466
+ asDefault: true,
1467
+ optionAlias: '-c, --cmd <command...>',
1468
+ }))
1469
+ .use(batchPlugin());
1470
+ },
1471
+ });
1477
1472
  void main();
@@ -190,8 +190,7 @@ const loadHandlers = async (root, app) => {
190
190
  if (typeof handler !== 'function')
191
191
  continue;
192
192
  for (const evt of def.events) {
193
- const httpEvt = evt
194
- .http;
193
+ const httpEvt = evt.http;
195
194
  const method = (httpEvt?.method ?? '').toUpperCase();
196
195
  const pattern = '/' + (httpEvt?.path ?? '').replace(/^\/+/, '');
197
196
  if (!method || !pattern)
package/dist/mjs/index.js CHANGED
@@ -865,14 +865,13 @@ function wrapHandler(functionConfig, business, opts) {
865
865
  assertKeysSubset(envConfig.stage.paramsSchema, envConfig.stage.envKeys, 'stage.envKeys');
866
866
  return async (event, context) => {
867
867
  // Compose typed env schema and parse process.env
868
- const all = deriveAllKeys(envConfig.global.envKeys, envConfig.stage.envKeys, (functionConfig.fnEnvKeys ?? []));
868
+ const all = deriveAllKeys(envConfig.global.envKeys, envConfig.stage.envKeys, functionConfig.fnEnvKeys ?? []);
869
869
  const { globalPick, stagePick } = splitKeysBySchema(all, envConfig.global.paramsSchema, envConfig.stage.paramsSchema);
870
870
  const envSchema = buildEnvSchema(globalPick, stagePick, envConfig.global.paramsSchema, envConfig.stage.paramsSchema);
871
871
  const env = parseTypedEnv(envSchema, process.env);
872
872
  const logger = console;
873
873
  // Non-HTTP: call business directly
874
- const httpTokens = opts?.httpEventTypeTokens ??
875
- defaultHttpEventTypeTokens;
874
+ const httpTokens = opts?.httpEventTypeTokens ?? defaultHttpEventTypeTokens;
876
875
  const isHttp = httpTokens.includes(functionConfig.eventType);
877
876
  if (!isHttp) {
878
877
  return business(event, context, { env, logger });
@@ -935,9 +934,7 @@ const createRegistry = (deps) => {
935
934
  ...(options.basePath ? { basePath: options.basePath } : {}),
936
935
  ...(options.httpContexts ? { httpContexts: options.httpContexts } : {}),
937
936
  ...(options.contentType ? { contentType: options.contentType } : {}),
938
- ...(mergedFnEnvKeys.length
939
- ? { fnEnvKeys: mergedFnEnvKeys }
940
- : {}),
937
+ ...(mergedFnEnvKeys.length ? { fnEnvKeys: mergedFnEnvKeys } : {}),
941
938
  ...(options.eventSchema ? { eventSchema: options.eventSchema } : {}),
942
939
  ...(options.responseSchema
943
940
  ? { responseSchema: options.responseSchema }
@@ -954,9 +951,7 @@ const createRegistry = (deps) => {
954
951
  ...(options.basePath ? { basePath: options.basePath } : {}),
955
952
  ...(options.httpContexts ? { httpContexts: options.httpContexts } : {}),
956
953
  ...(options.contentType ? { contentType: options.contentType } : {}),
957
- ...(mergedFnEnvKeys.length
958
- ? { fnEnvKeys: mergedFnEnvKeys }
959
- : {}),
954
+ ...(mergedFnEnvKeys.length ? { fnEnvKeys: mergedFnEnvKeys } : {}),
960
955
  ...(options.eventSchema ? { eventSchema: options.eventSchema } : {}),
961
956
  ...(options.responseSchema
962
957
  ? { responseSchema: options.responseSchema }
@@ -1123,7 +1118,7 @@ const buildAllServerlessFunctions = (registry, serverless, buildFnEnv) => {
1123
1118
  .split(sep)
1124
1119
  .join('/');
1125
1120
  const handler = `${handlerFileRel}.${serverless.defaultHandlerFileExport}`;
1126
- let events = [];
1121
+ let events;
1127
1122
  try {
1128
1123
  const { method, basePath, contexts } = resolveHttpFromFunctionConfig({
1129
1124
  functionName: r.functionName,
package/package.json CHANGED
@@ -7,70 +7,69 @@
7
7
  "hideCredit": true
8
8
  },
9
9
  "bin": {
10
- "smoz": "dist/cli/index.cjs"
10
+ "smoz": "dist/cli/index.mjs"
11
11
  },
12
12
  "bugs": {
13
13
  "url": "https://github.com/karmaniverous/smoz/issues"
14
14
  },
15
15
  "dependencies": {
16
- "@karmaniverous/get-dotenv": "^5.2.6",
17
- "@middy/core": "^6.4.5",
18
- "@middy/http-content-negotiation": "^6.4.5",
19
- "@middy/http-cors": "^6.4.5",
20
- "@middy/http-error-handler": "^6.4.5",
21
- "@middy/http-event-normalizer": "^6.4.5",
22
- "@middy/http-header-normalizer": "^6.4.5",
23
- "@middy/http-json-body-parser": "^6.4.5",
24
- "@middy/http-response-serializer": "^6.4.5",
16
+ "@karmaniverous/get-dotenv": "^7.0.11",
17
+ "@middy/core": "^7.7.0",
18
+ "@middy/http-content-negotiation": "^7.7.0",
19
+ "@middy/http-cors": "^7.7.0",
20
+ "@middy/http-error-handler": "^7.7.0",
21
+ "@middy/http-event-normalizer": "^7.7.0",
22
+ "@middy/http-header-normalizer": "^7.7.0",
23
+ "@middy/http-json-body-parser": "^7.7.0",
24
+ "@middy/http-response-serializer": "^7.7.0",
25
25
  "aws-lambda": "^1.0.7",
26
- "chokidar": "^4.0.3",
26
+ "chokidar": "^5.0.0",
27
27
  "http-errors": "^2.0.1",
28
- "package-directory": "^8.1.0",
28
+ "package-directory": "^8.2.0",
29
29
  "radash": "^12.1.1",
30
- "zod": "^4.1.13"
30
+ "zod": "^4.4.3"
31
31
  },
32
32
  "description": "John Galt Services back end.",
33
33
  "devDependencies": {
34
- "@dotenvx/dotenvx": "^1.51.1",
35
- "@eslint/js": "^9.39.1",
34
+ "@dotenvx/dotenvx": "^2.10.0",
35
+ "@eslint/js": "^10.0.1",
36
36
  "@rollup/plugin-alias": "^6.0.0",
37
37
  "@rollup/plugin-typescript": "^12.3.0",
38
- "@serverless/typescript": "^4.23.0",
39
- "@types/aws-lambda": "^8.10.159",
38
+ "@serverless/typescript": "^4.30.0",
39
+ "@types/aws-lambda": "^8.10.162",
40
40
  "@types/fs-extra": "^11.0.4",
41
41
  "@types/http-errors": "^2.0.5",
42
- "@types/node": "^24",
43
- "@types/serverless": "^3.12.27",
44
- "@vitest/coverage-v8": "^4.0.13",
45
- "auto-changelog": "^2.5.0",
46
- "eslint": "^9.39.1",
42
+ "@types/node": "^26",
43
+ "@types/serverless": "^3.12.28",
44
+ "@vitest/coverage-v8": "^4.1.10",
45
+ "auto-changelog": "^2.6.0",
46
+ "eslint": "^10.7.0",
47
47
  "eslint-config-prettier": "^10.1.8",
48
- "eslint-plugin-prettier": "^5.5.4",
49
- "eslint-plugin-simple-import-sort": "^12.1.1",
50
- "fs-extra": "^11.3.2",
51
- "knip": "^5.70.2",
52
- "lefthook": "^2.0.4",
53
- "prettier": "^3.6.2",
54
- "release-it": "^19.0.6",
55
- "rimraf": "^6.1.2",
56
- "rollup": "^4.53.3",
57
- "rollup-plugin-dts": "^6.2.3",
58
- "serverless": "^4.25.0",
48
+ "eslint-plugin-prettier": "^5.5.6",
49
+ "eslint-plugin-simple-import-sort": "^13.0.0",
50
+ "fs-extra": "^11.3.6",
51
+ "knip": "^6.27.0",
52
+ "lefthook": "^2.1.10",
53
+ "prettier": "^3.9.5",
54
+ "release-it": "^20.2.1",
55
+ "rimraf": "^6.1.3",
56
+ "rollup": "^4.62.2",
57
+ "rollup-plugin-dts": "^6.4.1",
58
+ "serverless": "^4.39.0",
59
59
  "serverless-apigateway-log-retention": "^1.1.0",
60
60
  "serverless-deployment-bucket": "^1.6.0",
61
- "serverless-domain-manager": "^8.0.0",
62
- "serverless-offline": "^14.4.0",
61
+ "serverless-domain-manager": "^10.2.0",
62
+ "serverless-offline": "^14.7.4",
63
63
  "serverless-plugin-common-excludes": "^4.0.0",
64
- "tsx": "^4.20.6",
65
- "typedoc": "^0.28.14",
66
- "typedoc-plugin-mdn-links": "^5.0.10",
64
+ "tsx": "^4.23.1",
65
+ "typedoc": "^0.28.20",
66
+ "typedoc-plugin-mdn-links": "^5.1.1",
67
67
  "typedoc-plugin-replace-text": "^4.2.0",
68
68
  "typedoc-plugin-zod": "^1.4.3",
69
- "typescript": "^5.9.3",
70
- "typescript-eslint": "^8.48.0",
71
- "vite-tsconfig-paths": "^5.1.4",
72
- "vitest": "^4.0.13",
73
- "zod-openapi": "^5.4.3"
69
+ "typescript": "^6.0.3",
70
+ "typescript-eslint": "^8.64.0",
71
+ "vitest": "^4.1.10",
72
+ "zod-openapi": "^6.0.0"
74
73
  },
75
74
  "engines": {
76
75
  "node": ">=22.19.0"
@@ -184,7 +183,7 @@
184
183
  "templates:lint": "eslint --fix -c templates/default/eslint.config.ts \"templates/default/**/*.{ts,tsx,js,jsx}\" \"templates/default/eslint.config.ts\""
185
184
  },
186
185
  "type": "module",
187
- "version": "0.2.13",
186
+ "version": "0.2.15",
188
187
  "volta": {
189
188
  "node": "22.19.0"
190
189
  }