@karmaniverous/smoz 0.2.9 → 0.2.11
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/LICENSE +28 -28
- package/README.md +82 -81
- package/dist/cjs/index.js +20 -10
- package/dist/cli/index.cjs +262 -278
- package/dist/index.d.ts +44 -36
- package/dist/mjs/index.js +20 -10
- package/package.json +36 -37
- package/templates/default/.vscode/extensions.json +8 -8
- package/templates/default/README.md +98 -96
- package/templates/default/app/config/app.config.ts +3 -2
- package/templates/default/app/generated/openapi.json +8 -8
- package/templates/default/eslint.config.ts +0 -1
- package/templates/default/gitignore +42 -42
- package/templates/default/package.json +6 -7
- package/templates/default/tsconfig.base.json +21 -21
- package/templates/default/tsconfig.downstream.json +13 -13
- package/templates/default/tsconfig.eslint.json +2 -2
- package/templates/default/tsconfig.json +25 -25
- package/templates/default/tsdoc.json +46 -46
- package/templates/default/typedoc.json +14 -14
package/dist/cli/index.cjs
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
+
var cliHost = require('@karmaniverous/get-dotenv/cliHost');
|
|
5
|
+
var plugins = require('@karmaniverous/get-dotenv/plugins');
|
|
6
|
+
var packageDirectory = require('package-directory');
|
|
4
7
|
var fs = require('node:fs');
|
|
5
8
|
var path = require('node:path');
|
|
6
|
-
var commander = require('commander');
|
|
7
|
-
var packageDirectory = require('package-directory');
|
|
8
9
|
var chokidar = require('chokidar');
|
|
9
10
|
var node_child_process = require('node:child_process');
|
|
10
|
-
var
|
|
11
|
+
var getDotenv = require('@karmaniverous/get-dotenv');
|
|
11
12
|
var node_url = require('node:url');
|
|
12
13
|
var node_process = require('node:process');
|
|
13
14
|
var promises = require('node:readline/promises');
|
|
@@ -82,8 +83,8 @@ import { z } from 'zod';
|
|
|
82
83
|
|
|
83
84
|
import { app, APP_ROOT_ABS } from '@/app/config/app.config';
|
|
84
85
|
|
|
85
|
-
export const eventSchema = z.
|
|
86
|
-
export const responseSchema = z.
|
|
86
|
+
export const eventSchema = z.unknown();
|
|
87
|
+
export const responseSchema = z.unknown();
|
|
87
88
|
|
|
88
89
|
export const fn = app.defineFunction({
|
|
89
90
|
eventType: '${token}',
|
|
@@ -153,8 +154,8 @@ import { z } from 'zod';
|
|
|
153
154
|
|
|
154
155
|
import { app, APP_ROOT_ABS } from '@/app/config/app.config';
|
|
155
156
|
|
|
156
|
-
export const eventSchema = z.
|
|
157
|
-
export const responseSchema = z.
|
|
157
|
+
export const eventSchema = z.unknown();
|
|
158
|
+
export const responseSchema = z.unknown();
|
|
158
159
|
|
|
159
160
|
export const fn = app.defineFunction({
|
|
160
161
|
eventType: '${token}',
|
|
@@ -281,7 +282,7 @@ const launchOffline = async (root, opts) => {
|
|
|
281
282
|
}
|
|
282
283
|
return { cmd, args, shell };
|
|
283
284
|
};
|
|
284
|
-
let child = spawnOffline(root, makeCmd(), opts.verbose);
|
|
285
|
+
let child = await spawnOffline(root, makeCmd(), opts.verbose);
|
|
285
286
|
const close = async () => new Promise((resolve) => {
|
|
286
287
|
if (child.killed) {
|
|
287
288
|
resolve();
|
|
@@ -298,46 +299,23 @@ const launchOffline = async (root, opts) => {
|
|
|
298
299
|
});
|
|
299
300
|
const restart = async () => {
|
|
300
301
|
await close();
|
|
301
|
-
child = spawnOffline(root, makeCmd(), opts.verbose);
|
|
302
|
+
child = await spawnOffline(root, makeCmd(), opts.verbose);
|
|
302
303
|
};
|
|
303
304
|
return { restart, close };
|
|
304
305
|
};
|
|
305
|
-
const spawnOffline = (root, cmd, verbose) => {
|
|
306
|
-
//
|
|
307
|
-
// Some toolchains (e.g., tsx/esbuild invoked under offline) interpolate TEMP/TMP,
|
|
308
|
-
// and if they are undefined, they may create literal "undefined\\temp\\..." paths
|
|
309
|
-
// relative to CWD. Use os.tmpdir() as a safe fallback.
|
|
306
|
+
const spawnOffline = async (root, cmd, verbose) => {
|
|
307
|
+
// Normalize child environment via get-dotenv when available; safe fallback otherwise.
|
|
310
308
|
const baseEnv = { ...process.env };
|
|
311
|
-
const
|
|
312
|
-
// Cross-platform default
|
|
313
|
-
if (!baseEnv.TMPDIR)
|
|
314
|
-
baseEnv.TMPDIR = tmp;
|
|
315
|
-
// Windows defaults
|
|
316
|
-
if (process.platform === 'win32') {
|
|
317
|
-
if (!baseEnv.TEMP)
|
|
318
|
-
baseEnv.TEMP = tmp;
|
|
319
|
-
if (!baseEnv.TMP)
|
|
320
|
-
baseEnv.TMP = tmp;
|
|
321
|
-
// Some nested toolchains derive cache roots from these when TMP/TEMP are absent
|
|
322
|
-
if (!baseEnv.LOCALAPPDATA)
|
|
323
|
-
baseEnv.LOCALAPPDATA = tmp;
|
|
324
|
-
if (!baseEnv.USERPROFILE)
|
|
325
|
-
baseEnv.USERPROFILE = tmp;
|
|
326
|
-
}
|
|
327
|
-
else {
|
|
328
|
-
// POSIX: some tools fall back to HOME for caches when TMPDIR isn't used
|
|
329
|
-
if (!baseEnv.HOME)
|
|
330
|
-
baseEnv.HOME = tmp;
|
|
331
|
-
}
|
|
309
|
+
const childEnv = getDotenv.buildSpawnEnv(baseEnv);
|
|
332
310
|
// Optional diagnostics to verify the child sees sane temp-related envs
|
|
333
311
|
if (verbose) {
|
|
334
312
|
const snap = {
|
|
335
|
-
TMPDIR:
|
|
336
|
-
TEMP:
|
|
337
|
-
TMP:
|
|
338
|
-
HOME:
|
|
339
|
-
USERPROFILE:
|
|
340
|
-
LOCALAPPDATA:
|
|
313
|
+
TMPDIR: childEnv.TMPDIR,
|
|
314
|
+
TEMP: childEnv.TEMP,
|
|
315
|
+
TMP: childEnv.TMP,
|
|
316
|
+
HOME: childEnv.HOME,
|
|
317
|
+
USERPROFILE: childEnv.USERPROFILE,
|
|
318
|
+
LOCALAPPDATA: childEnv.LOCALAPPDATA,
|
|
341
319
|
};
|
|
342
320
|
process.stdout.write(`[offline] env snapshot: ${JSON.stringify(snap)}\n`);
|
|
343
321
|
}
|
|
@@ -345,7 +323,7 @@ const spawnOffline = (root, cmd, verbose) => {
|
|
|
345
323
|
cwd: root,
|
|
346
324
|
shell: cmd.shell,
|
|
347
325
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
348
|
-
env:
|
|
326
|
+
env: childEnv,
|
|
349
327
|
});
|
|
350
328
|
const prefix = '[offline] ';
|
|
351
329
|
let announced = false;
|
|
@@ -375,9 +353,12 @@ const spawnOffline = (root, cmd, verbose) => {
|
|
|
375
353
|
return child;
|
|
376
354
|
};
|
|
377
355
|
|
|
378
|
-
|
|
379
|
-
*
|
|
380
|
-
*
|
|
356
|
+
/**
|
|
357
|
+
* @module OpenAPI one-shot runner.
|
|
358
|
+
*
|
|
359
|
+
* Spawns the project-local OpenAPI script via tsx, then formats the output
|
|
360
|
+
* with prettier. Owns the full generate-and-format cycle so consumers can
|
|
361
|
+
* use a single `smoz openapi` invocation.
|
|
381
362
|
*/
|
|
382
363
|
const findTsxCli = (root) => {
|
|
383
364
|
// Prefer invoking the JS entry to avoid shell .cmd quirks on Windows.
|
|
@@ -393,6 +374,17 @@ const findTsxCli = (root) => {
|
|
|
393
374
|
const cmd = process.platform === 'win32' ? 'tsx.cmd' : 'tsx';
|
|
394
375
|
return { cmd, args: ['app/config/openapi.ts'], shell: true };
|
|
395
376
|
};
|
|
377
|
+
const findPrettierCli = (root) => {
|
|
378
|
+
const js = path.resolve(root, 'node_modules', 'prettier', 'bin', 'prettier.cjs');
|
|
379
|
+
if (!fs.existsSync(js)) {
|
|
380
|
+
throw new Error(`prettier not found at ${js}. Install it as a devDependency.`);
|
|
381
|
+
}
|
|
382
|
+
return {
|
|
383
|
+
cmd: process.execPath,
|
|
384
|
+
args: [js, '--write', 'app/generated/openapi.json'],
|
|
385
|
+
shell: false,
|
|
386
|
+
};
|
|
387
|
+
};
|
|
396
388
|
const runOpenapi = async (root, opts) => {
|
|
397
389
|
// Detect changes by comparing the pre/post content of app/generated/openapi.json.
|
|
398
390
|
const outFile = path.resolve(root, 'app', 'generated', 'openapi.json');
|
|
@@ -405,18 +397,47 @@ const runOpenapi = async (root, opts) => {
|
|
|
405
397
|
// ignore read errors; treat as absent
|
|
406
398
|
before = undefined;
|
|
407
399
|
}
|
|
408
|
-
|
|
400
|
+
// Build a normalized child env via get-dotenv (static import).
|
|
401
|
+
const env = getDotenv.buildSpawnEnv({});
|
|
402
|
+
// Step 1: generate the OpenAPI spec.
|
|
403
|
+
const tsx = findTsxCli(root);
|
|
409
404
|
if (opts?.verbose) {
|
|
410
|
-
console.log(`[openapi] ${[cmd, ...args].join(' ')}`);
|
|
405
|
+
console.log(`[openapi] ${[tsx.cmd, ...tsx.args].join(' ')}`);
|
|
411
406
|
}
|
|
412
|
-
const
|
|
407
|
+
const genRes = node_child_process.spawnSync(tsx.cmd, tsx.args, {
|
|
413
408
|
cwd: root,
|
|
414
409
|
stdio: 'inherit',
|
|
415
|
-
shell,
|
|
410
|
+
shell: tsx.shell,
|
|
411
|
+
env,
|
|
416
412
|
});
|
|
417
|
-
if (typeof
|
|
418
|
-
const code = typeof
|
|
419
|
-
|
|
413
|
+
if (typeof genRes.status !== 'number' || genRes.status !== 0) {
|
|
414
|
+
const code = typeof genRes.status === 'number' ? String(genRes.status) : 'unknown';
|
|
415
|
+
const detail = genRes.error
|
|
416
|
+
? `: ${genRes.error.message}`
|
|
417
|
+
: genRes.signal
|
|
418
|
+
? ` (signal ${genRes.signal})`
|
|
419
|
+
: '';
|
|
420
|
+
throw new Error(`openapi failed (exit ${code})${detail}`);
|
|
421
|
+
}
|
|
422
|
+
// Step 2: format the output with prettier.
|
|
423
|
+
const prettier = findPrettierCli(root);
|
|
424
|
+
if (opts?.verbose) {
|
|
425
|
+
console.log(`[openapi] ${[prettier.cmd, ...prettier.args].join(' ')}`);
|
|
426
|
+
}
|
|
427
|
+
const fmtRes = node_child_process.spawnSync(prettier.cmd, prettier.args, {
|
|
428
|
+
cwd: root,
|
|
429
|
+
stdio: 'inherit',
|
|
430
|
+
shell: prettier.shell,
|
|
431
|
+
env,
|
|
432
|
+
});
|
|
433
|
+
if (typeof fmtRes.status !== 'number' || fmtRes.status !== 0) {
|
|
434
|
+
const code = typeof fmtRes.status === 'number' ? String(fmtRes.status) : 'unknown';
|
|
435
|
+
const detail = fmtRes.error
|
|
436
|
+
? `: ${fmtRes.error.message}`
|
|
437
|
+
: fmtRes.signal
|
|
438
|
+
? ` (signal ${fmtRes.signal})`
|
|
439
|
+
: '';
|
|
440
|
+
throw new Error(`prettier format failed (exit ${code})${detail}`);
|
|
420
441
|
}
|
|
421
442
|
// Determine whether the file content changed
|
|
422
443
|
try {
|
|
@@ -548,6 +569,50 @@ const inferDefaultStage = (root, verbose) => {
|
|
|
548
569
|
console.log('[dev] inferring stage: dev (explicit --stage overrides)');
|
|
549
570
|
return 'dev';
|
|
550
571
|
};
|
|
572
|
+
/**
|
|
573
|
+
* Resolve the effective stage for dev according to precedence:
|
|
574
|
+
* 1) CLI --stage
|
|
575
|
+
* 2) getdotenv.config.json -> plugins.smoz.stage (string)
|
|
576
|
+
* 3) process.env.STAGE
|
|
577
|
+
* 4) inferDefaultStage() fallback ("dev")
|
|
578
|
+
*
|
|
579
|
+
* Notes:
|
|
580
|
+
* - We intentionally read only the JSON variant to avoid adding YAML/TS loaders
|
|
581
|
+
* to the CLI runtime. JS/TS config will be handled by the get-dotenv host
|
|
582
|
+
* once the full plugin-first path is wired; this probe is a safe, minimal
|
|
583
|
+
* bridge that preserves current behavior when the file is absent.
|
|
584
|
+
*/
|
|
585
|
+
const resolveStage = async (root, cliStage, verbose) => {
|
|
586
|
+
if (typeof cliStage === 'string' && cliStage.trim().length > 0) {
|
|
587
|
+
if (verbose)
|
|
588
|
+
console.log(`[dev] stage (cli): ${cliStage}`);
|
|
589
|
+
return cliStage;
|
|
590
|
+
}
|
|
591
|
+
// getdotenv.config.json → plugins.smoz.stage
|
|
592
|
+
try {
|
|
593
|
+
const cfgPath = path.resolve(root, 'getdotenv.config.json');
|
|
594
|
+
if (fs.existsSync(cfgPath)) {
|
|
595
|
+
const raw = fs.readFileSync(cfgPath, 'utf8');
|
|
596
|
+
const parsed = JSON.parse(raw);
|
|
597
|
+
const fromCfg = parsed.plugins?.smoz?.stage;
|
|
598
|
+
if (typeof fromCfg === 'string' && fromCfg.trim().length > 0) {
|
|
599
|
+
if (verbose)
|
|
600
|
+
console.log(`[dev] stage (config): ${fromCfg}`);
|
|
601
|
+
return fromCfg.trim();
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
catch {
|
|
606
|
+
// best-effort; fall through
|
|
607
|
+
}
|
|
608
|
+
const envStage = typeof process.env.STAGE === 'string' ? process.env.STAGE : undefined;
|
|
609
|
+
if (envStage && envStage.trim().length > 0) {
|
|
610
|
+
if (verbose)
|
|
611
|
+
console.log(`[dev] stage (env): ${envStage}`);
|
|
612
|
+
return envStage.trim();
|
|
613
|
+
}
|
|
614
|
+
return inferDefaultStage(root, verbose);
|
|
615
|
+
};
|
|
551
616
|
const seedEnvForStage = async (root, stage, verbose) => {
|
|
552
617
|
// Best effort: import the app config to read declared env keys and concrete values.
|
|
553
618
|
// Preserve existing process.env values; only seed when unset.
|
|
@@ -653,21 +718,24 @@ const launchInline = async (root, opts) => {
|
|
|
653
718
|
const { entry } = resolveInlineEntry(pkgRoot);
|
|
654
719
|
const makeTsx = () => {
|
|
655
720
|
const { cmd, args, shell } = resolveTsxCommand(root, entry);
|
|
656
|
-
return
|
|
657
|
-
|
|
658
|
-
stdio: 'inherit',
|
|
659
|
-
shell,
|
|
660
|
-
// Enable tsconfig paths for "@/..." during TS fallback.
|
|
661
|
-
env: {
|
|
721
|
+
return (async () => {
|
|
722
|
+
const env = getDotenv.buildSpawnEnv({
|
|
662
723
|
...process.env,
|
|
724
|
+
// Enable tsconfig paths for "@/..." during TS fallback.
|
|
663
725
|
TSX_TSCONFIG_PATHS: '1',
|
|
664
726
|
SMOZ_STAGE: opts.stage,
|
|
665
727
|
SMOZ_PORT: String(opts.port),
|
|
666
728
|
SMOZ_VERBOSE: opts.verbose ? '1' : '',
|
|
667
|
-
}
|
|
668
|
-
|
|
729
|
+
});
|
|
730
|
+
return node_child_process.spawn(cmd, args, {
|
|
731
|
+
cwd: root,
|
|
732
|
+
stdio: 'inherit',
|
|
733
|
+
shell,
|
|
734
|
+
env,
|
|
735
|
+
});
|
|
736
|
+
})();
|
|
669
737
|
};
|
|
670
|
-
let child = makeTsx();
|
|
738
|
+
let child = await makeTsx();
|
|
671
739
|
const close = async () => new Promise((resolve) => {
|
|
672
740
|
// If the process has already exited (exitCode set), resolve immediately.
|
|
673
741
|
if (child.exitCode !== null) {
|
|
@@ -684,16 +752,14 @@ const launchInline = async (root, opts) => {
|
|
|
684
752
|
});
|
|
685
753
|
const restart = async () => {
|
|
686
754
|
await close();
|
|
687
|
-
child = makeTsx();
|
|
755
|
+
child = await makeTsx();
|
|
688
756
|
};
|
|
689
757
|
return { close, restart };
|
|
690
758
|
};
|
|
691
759
|
|
|
692
760
|
const runDev = async (root, opts) => {
|
|
693
761
|
const verbose = !!opts.verbose;
|
|
694
|
-
const stage = typeof opts.stage === 'string'
|
|
695
|
-
? opts.stage
|
|
696
|
-
: inferDefaultStage(root, verbose);
|
|
762
|
+
const stage = await resolveStage(root, typeof opts.stage === 'string' ? opts.stage : undefined, verbose);
|
|
697
763
|
// Seed env with concrete values for the selected stage.
|
|
698
764
|
try {
|
|
699
765
|
await seedEnvForStage(root, stage, verbose);
|
|
@@ -1268,226 +1334,144 @@ const runInit = async (root, template = 'default', opts) => {
|
|
|
1268
1334
|
};
|
|
1269
1335
|
|
|
1270
1336
|
/**
|
|
1271
|
-
* SMOZ
|
|
1337
|
+
* SMOZ command plugin (get-dotenv plugin-first host).
|
|
1272
1338
|
*
|
|
1273
|
-
*
|
|
1274
|
-
*
|
|
1275
|
-
* - dev: watch loop orchestrator for register/openapi and optional local serving
|
|
1276
|
-
* - add: scaffold a new function skeleton under app/functions
|
|
1339
|
+
* Registers init/add/register/openapi/dev on a GetDotenvCli instance and
|
|
1340
|
+
* delegates to existing implementations. No business logic here.
|
|
1277
1341
|
*/
|
|
1278
|
-
const
|
|
1279
|
-
const
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
}
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
if (ua.includes('pnpm'))
|
|
1307
|
-
return 'pnpm';
|
|
1308
|
-
if (ua.includes('yarn'))
|
|
1309
|
-
return 'yarn';
|
|
1310
|
-
if (ua.includes('npm'))
|
|
1311
|
-
return 'npm';
|
|
1312
|
-
return ua || undefined;
|
|
1313
|
-
};
|
|
1314
|
-
const detectStanPath = (root) => {
|
|
1315
|
-
// Keep this conservative; future: read stan.config.* if introduced.
|
|
1316
|
-
const candidate = '.stan';
|
|
1317
|
-
if (fs.existsSync(path.join(root, candidate, 'system', 'stan.system.md'))) {
|
|
1318
|
-
return candidate;
|
|
1319
|
-
}
|
|
1320
|
-
return candidate; // default
|
|
1321
|
-
};
|
|
1322
|
-
const printSignature = () => {
|
|
1323
|
-
const root = getRepoRoot();
|
|
1324
|
-
const pkg = readPkg(root);
|
|
1325
|
-
const name = pkg.name ?? 'smoz';
|
|
1326
|
-
const version = pkg.version ?? '0.0.0';
|
|
1327
|
-
const pm = detectPackageManager();
|
|
1328
|
-
const stanPath = detectStanPath(root);
|
|
1329
|
-
const hasAppConfig = fs.existsSync(path.join(root, 'app', 'config', 'app.config.ts'));
|
|
1330
|
-
const hasSmozJson = fs.existsSync(path.join(root, 'smoz.config.json'));
|
|
1331
|
-
const hasSmozYaml = fs.existsSync(path.join(root, 'smoz.config.yml')) ||
|
|
1332
|
-
fs.existsSync(path.join(root, 'smoz.config.yaml'));
|
|
1333
|
-
console.log(`${name} v${version}`);
|
|
1334
|
-
console.log(`Node ${process.version}`);
|
|
1335
|
-
console.log(`Repo: ${root}`);
|
|
1336
|
-
console.log(`stanPath: ${stanPath}`);
|
|
1337
|
-
console.log(`app/config/app.config.ts: ${hasAppConfig ? 'found' : 'missing'}`);
|
|
1338
|
-
console.log(`smoz.config.*: ${hasSmozJson || hasSmozYaml ? 'found' : 'absent'}`);
|
|
1339
|
-
if (pm)
|
|
1340
|
-
console.log(`PM: ${pm}`);
|
|
1341
|
-
};
|
|
1342
|
-
const main = () => {
|
|
1343
|
-
const root = getRepoRoot();
|
|
1344
|
-
const pkg = readPkg(root);
|
|
1345
|
-
const program = new commander.Command();
|
|
1346
|
-
program
|
|
1347
|
-
.name('smoz')
|
|
1348
|
-
.description('SMOZ CLI')
|
|
1349
|
-
// Add -v alias for version in addition to default --version behavior
|
|
1350
|
-
.version(pkg.version ?? '0.0.0', '-v, --version', 'output the version');
|
|
1351
|
-
program
|
|
1352
|
-
.command('add')
|
|
1353
|
-
.argument('<spec>', 'Add function: HTTP <eventType>/<segments...>/<method> or non-HTTP <eventType>/<segments...>')
|
|
1354
|
-
.description('Scaffold a new function under app/functions')
|
|
1355
|
-
.action(async (spec) => {
|
|
1356
|
-
try {
|
|
1357
|
-
const { created, skipped } = await runAdd(root, spec);
|
|
1358
|
-
console.log(created.length
|
|
1359
|
-
? `Created:\n - ${created.join('\n - ')}${skipped.length
|
|
1360
|
-
? `\nSkipped (exists):\n - ${skipped.join('\n - ')}`
|
|
1361
|
-
: ''}`
|
|
1362
|
-
: 'Nothing created (files already exist).');
|
|
1363
|
-
}
|
|
1364
|
-
catch (e) {
|
|
1365
|
-
console.error(e.message);
|
|
1366
|
-
process.exitCode = 1;
|
|
1367
|
-
}
|
|
1368
|
-
});
|
|
1369
|
-
program
|
|
1370
|
-
.command('init')
|
|
1371
|
-
.description('Scaffold a new SMOZ app from packaged templates (default: default)')
|
|
1372
|
-
.option('-t, --template <nameOrPath>', 'Template name or directory path', 'default')
|
|
1373
|
-
.option('-i, --install [pm]', 'Install dependencies (optionally specify pm: npm|pnpm|yarn|bun)')
|
|
1374
|
-
.option('--no-install', 'Skip dependency installation (overrides -y)', false)
|
|
1375
|
-
.option('-y, --yes', 'Skip prompts (non-interactive)', false)
|
|
1376
|
-
.option('--dry-run', 'Show planned actions without writing', false)
|
|
1377
|
-
.action(async (opts) => {
|
|
1378
|
-
try {
|
|
1379
|
-
const cfg = readSmozConfig(root).cliDefaults?.init ?? {};
|
|
1380
|
-
// Resolve template: CLI > config > default
|
|
1381
|
-
const tpl = typeof opts.template === 'string'
|
|
1382
|
-
? opts.template
|
|
1383
|
-
: typeof cfg.template === 'string'
|
|
1384
|
-
? cfg.template
|
|
1385
|
-
: 'default';
|
|
1386
|
-
// Resolve install behavior: CLI > --no-install > config
|
|
1387
|
-
let install = opts.install;
|
|
1388
|
-
if (opts.noInstall === true) {
|
|
1389
|
-
install = false;
|
|
1390
|
-
}
|
|
1391
|
-
else if (install === undefined) {
|
|
1392
|
-
const d = cfg.install;
|
|
1393
|
-
install =
|
|
1394
|
-
d === 'auto'
|
|
1395
|
-
? true
|
|
1396
|
-
: d === 'none'
|
|
1397
|
-
? false
|
|
1398
|
-
: typeof d === 'string'
|
|
1399
|
-
? d
|
|
1400
|
-
: undefined;
|
|
1401
|
-
}
|
|
1402
|
-
const conflict = typeof opts.conflict === 'string' ? opts.conflict : cfg.onConflict;
|
|
1403
|
-
const { created, skipped, examples, merged, installed } = await runInit(root, tpl, {
|
|
1404
|
-
// Include only when defined to satisfy exactOptionalPropertyTypes
|
|
1405
|
-
...(install !== undefined ? { install } : {}),
|
|
1406
|
-
...(typeof conflict === 'string' ? { conflict } : {}),
|
|
1407
|
-
yes: opts.yes === true,
|
|
1408
|
-
noInstall: opts.noInstall === true,
|
|
1409
|
-
dryRun: opts.dryRun === true,
|
|
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 } : {}),
|
|
1410
1370
|
});
|
|
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
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
.action(async (opts) => {
|
|
1465
|
-
try {
|
|
1466
|
-
const cfg = readSmozConfig(root).cliDefaults?.dev ?? {};
|
|
1467
|
-
// Resolve local mode default from config if not provided
|
|
1468
|
-
const localResolved = typeof opts.local === 'string'
|
|
1469
|
-
? opts.local
|
|
1470
|
-
: opts.local === false
|
|
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 });
|
|
1400
|
+
});
|
|
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
|
|
1471
1424
|
? false
|
|
1472
|
-
:
|
|
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;
|
|
1473
1440
|
await runDev(root, {
|
|
1474
|
-
register
|
|
1475
|
-
openapi
|
|
1476
|
-
local
|
|
1441
|
+
register,
|
|
1442
|
+
openapi,
|
|
1443
|
+
local,
|
|
1477
1444
|
...(typeof opts.stage === 'string' ? { stage: opts.stage } : {}),
|
|
1478
|
-
port
|
|
1445
|
+
...(typeof port === 'number' ? { port } : {}),
|
|
1479
1446
|
verbose: !!opts.verbose,
|
|
1480
1447
|
});
|
|
1481
|
-
}
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1448
|
+
});
|
|
1449
|
+
},
|
|
1450
|
+
});
|
|
1451
|
+
|
|
1452
|
+
/**
|
|
1453
|
+
* SMOZ CLI — plugin-first host built on get-dotenv.
|
|
1454
|
+
*
|
|
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.
|
|
1458
|
+
*/
|
|
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',
|
|
1490
1464
|
});
|
|
1491
|
-
|
|
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();
|
|
1492
1476
|
};
|
|
1493
|
-
main();
|
|
1477
|
+
void main();
|