@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 +5 -10
- package/dist/cli/{index.cjs → index.mjs} +256 -261
- package/dist/mjs/cli/inline-server.js +1 -2
- package/dist/mjs/index.js +5 -10
- package/package.json +43 -44
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,
|
|
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
|
-
|
|
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(
|
|
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 (
|
|
51
|
+
if (existsSync(outFile))
|
|
54
52
|
return { created: false };
|
|
55
|
-
await
|
|
56
|
-
await
|
|
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 =
|
|
205
|
-
const lambdaPath =
|
|
206
|
-
const handlerPath =
|
|
207
|
-
const openapiPath =
|
|
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(
|
|
250
|
+
created.push(posix.normalize(f.path));
|
|
253
251
|
else
|
|
254
|
-
skipped.push(
|
|
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 =
|
|
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 =
|
|
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 (
|
|
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 (!
|
|
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 (
|
|
394
|
-
before =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 (!
|
|
442
|
+
if (!existsSync(outFile))
|
|
445
443
|
return before !== undefined; // deleted vs existed
|
|
446
|
-
const after =
|
|
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(
|
|
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
|
|
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
|
|
491
|
-
await
|
|
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
|
|
493
|
+
const entries = await promises.readdir(dir, { withFileTypes: true });
|
|
496
494
|
for (const ent of entries) {
|
|
497
|
-
const p =
|
|
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 =
|
|
506
|
+
const base = join(root, 'app', 'functions');
|
|
509
507
|
const buckets = { lambda: [], openapi: [], serverless: [] };
|
|
510
|
-
if (!
|
|
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(
|
|
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(
|
|
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 =
|
|
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 =
|
|
551
|
-
const oaiPath =
|
|
552
|
-
const srvPath =
|
|
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(
|
|
556
|
+
wrote.push(posix.normalize(fnsPath));
|
|
559
557
|
if (await writeIfChanged(oaiPath, oai))
|
|
560
|
-
wrote.push(
|
|
558
|
+
wrote.push(posix.normalize(oaiPath));
|
|
561
559
|
if (await writeIfChanged(srvPath, srv))
|
|
562
|
-
wrote.push(
|
|
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 (
|
|
595
|
-
const raw =
|
|
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 =
|
|
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 =
|
|
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(
|
|
717
|
-
const pkgRoot =
|
|
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 =
|
|
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
|
|
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 (
|
|
894
|
+
if (existsSync(outFile))
|
|
897
895
|
return { created: false };
|
|
898
|
-
await
|
|
899
|
-
await
|
|
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
|
|
901
|
+
const entries = await promises.readdir(dir, { withFileTypes: true });
|
|
904
902
|
for (const ent of entries) {
|
|
905
|
-
const p =
|
|
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
|
|
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
|
|
926
|
-
await
|
|
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 =
|
|
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 =
|
|
958
|
-
const data = await
|
|
959
|
-
if (!
|
|
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(
|
|
960
|
+
created.push(posix.normalize(dest));
|
|
963
961
|
else
|
|
964
|
-
skipped.push(
|
|
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,
|
|
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
|
|
988
|
-
await
|
|
989
|
-
created.push(
|
|
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(
|
|
993
|
+
examples.push(posix.normalize(ex));
|
|
996
994
|
else
|
|
997
|
-
skipped.push(
|
|
995
|
+
skipped.push(posix.normalize(ex));
|
|
998
996
|
}
|
|
999
997
|
else {
|
|
1000
|
-
skipped.push(
|
|
998
|
+
skipped.push(posix.normalize(dest));
|
|
1001
999
|
}
|
|
1002
1000
|
}
|
|
1003
1001
|
};
|
|
1004
1002
|
|
|
1005
1003
|
const detectPm = (root) => {
|
|
1006
|
-
if (
|
|
1004
|
+
if (existsSync(join(root, 'pnpm-lock.yaml')))
|
|
1007
1005
|
return 'pnpm';
|
|
1008
|
-
if (
|
|
1006
|
+
if (existsSync(join(root, 'yarn.lock')))
|
|
1009
1007
|
return 'yarn';
|
|
1010
|
-
if (
|
|
1008
|
+
if (existsSync(join(root, 'package-lock.json')))
|
|
1011
1009
|
return 'npm';
|
|
1012
|
-
if (
|
|
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 =
|
|
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 =
|
|
1097
|
-
const toolkitPkg = await readJson(
|
|
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 =
|
|
1121
|
-
const pkgRoot =
|
|
1122
|
-
return
|
|
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 =
|
|
1127
|
+
const genDir = join(root, 'app', 'generated');
|
|
1130
1128
|
const seeds = [
|
|
1131
1129
|
{
|
|
1132
|
-
path:
|
|
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:
|
|
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:
|
|
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(
|
|
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(
|
|
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 =
|
|
1178
|
+
const templateIsPath = existsSync(template) && (await promises.stat(template)).isDirectory();
|
|
1181
1179
|
const srcBase = templateIsPath
|
|
1182
|
-
?
|
|
1183
|
-
:
|
|
1184
|
-
const projectBase =
|
|
1185
|
-
if (!
|
|
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 (
|
|
1187
|
+
if (existsSync(projectBase)) {
|
|
1190
1188
|
const rl = optAll.yes === true
|
|
1191
1189
|
? undefined
|
|
1192
|
-
:
|
|
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
|
-
:
|
|
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 =
|
|
1231
|
-
const giDot =
|
|
1232
|
-
if (
|
|
1233
|
-
if (!
|
|
1234
|
-
await
|
|
1235
|
-
created.push(
|
|
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 =
|
|
1239
|
-
if (!
|
|
1240
|
-
const data = await
|
|
1241
|
-
await
|
|
1242
|
-
examples.push(
|
|
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
|
|
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 =
|
|
1254
|
-
const dst =
|
|
1255
|
-
if (
|
|
1256
|
-
if (!
|
|
1257
|
-
await
|
|
1258
|
-
created.push(
|
|
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 =
|
|
1263
|
-
if (!
|
|
1264
|
-
const data = await
|
|
1265
|
-
await
|
|
1266
|
-
examples.push(
|
|
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
|
|
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 =
|
|
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(
|
|
1293
|
+
created.push(posix.normalize(pkgPath));
|
|
1296
1294
|
}
|
|
1297
1295
|
// 4) Merge manifest additively (prefer template's embedded package.json)
|
|
1298
|
-
const templatePkgPath =
|
|
1299
|
-
const manifest =
|
|
1296
|
+
const templatePkgPath = resolve(srcBase, 'package.json');
|
|
1297
|
+
const manifest = existsSync(templatePkgPath)
|
|
1300
1298
|
? await readJson(templatePkgPath)
|
|
1301
|
-
: await readJson(
|
|
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 = () =>
|
|
1343
|
-
const
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
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
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
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
|
-
* -
|
|
1456
|
-
* and
|
|
1457
|
-
* -
|
|
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 =
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
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,
|
|
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.
|
|
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": "^
|
|
17
|
-
"@middy/core": "^
|
|
18
|
-
"@middy/http-content-negotiation": "^
|
|
19
|
-
"@middy/http-cors": "^
|
|
20
|
-
"@middy/http-error-handler": "^
|
|
21
|
-
"@middy/http-event-normalizer": "^
|
|
22
|
-
"@middy/http-header-normalizer": "^
|
|
23
|
-
"@middy/http-json-body-parser": "^
|
|
24
|
-
"@middy/http-response-serializer": "^
|
|
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": "^
|
|
26
|
+
"chokidar": "^5.0.0",
|
|
27
27
|
"http-errors": "^2.0.1",
|
|
28
|
-
"package-directory": "^8.
|
|
28
|
+
"package-directory": "^8.2.0",
|
|
29
29
|
"radash": "^12.1.1",
|
|
30
|
-
"zod": "^4.
|
|
30
|
+
"zod": "^4.4.3"
|
|
31
31
|
},
|
|
32
32
|
"description": "John Galt Services back end.",
|
|
33
33
|
"devDependencies": {
|
|
34
|
-
"@dotenvx/dotenvx": "^
|
|
35
|
-
"@eslint/js": "^
|
|
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.
|
|
39
|
-
"@types/aws-lambda": "^8.10.
|
|
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": "^
|
|
43
|
-
"@types/serverless": "^3.12.
|
|
44
|
-
"@vitest/coverage-v8": "^4.
|
|
45
|
-
"auto-changelog": "^2.
|
|
46
|
-
"eslint": "^
|
|
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.
|
|
49
|
-
"eslint-plugin-simple-import-sort": "^
|
|
50
|
-
"fs-extra": "^11.3.
|
|
51
|
-
"knip": "^
|
|
52
|
-
"lefthook": "^2.
|
|
53
|
-
"prettier": "^3.
|
|
54
|
-
"release-it": "^
|
|
55
|
-
"rimraf": "^6.1.
|
|
56
|
-
"rollup": "^4.
|
|
57
|
-
"rollup-plugin-dts": "^6.
|
|
58
|
-
"serverless": "^4.
|
|
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": "^
|
|
62
|
-
"serverless-offline": "^14.4
|
|
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.
|
|
65
|
-
"typedoc": "^0.28.
|
|
66
|
-
"typedoc-plugin-mdn-links": "^5.
|
|
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": "^
|
|
70
|
-
"typescript-eslint": "^8.
|
|
71
|
-
"
|
|
72
|
-
"
|
|
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.
|
|
186
|
+
"version": "0.2.15",
|
|
188
187
|
"volta": {
|
|
189
188
|
"node": "22.19.0"
|
|
190
189
|
}
|