@karmaniverous/smoz 0.2.14 → 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/cli/{index.cjs → index.mjs} +138 -141
- package/package.json +2 -2
|
@@ -1,20 +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 cli = require('@karmaniverous/get-dotenv/cli');
|
|
5
|
-
var plugins = require('@karmaniverous/get-dotenv/plugins');
|
|
6
|
-
var cliHost = require('@karmaniverous/get-dotenv/cliHost');
|
|
7
|
-
var packageDirectory = require('package-directory');
|
|
8
|
-
var fs = require('node:fs');
|
|
9
|
-
var path = require('node:path');
|
|
10
|
-
var chokidar = require('chokidar');
|
|
11
|
-
var node_child_process = require('node:child_process');
|
|
12
|
-
var getDotenv = require('@karmaniverous/get-dotenv');
|
|
13
|
-
var node_url = require('node:url');
|
|
14
|
-
var node_process = require('node:process');
|
|
15
|
-
var promises = require('node:readline/promises');
|
|
16
|
-
|
|
17
|
-
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
18
15
|
/**
|
|
19
16
|
* SMOZ CLI: add
|
|
20
17
|
*
|
|
@@ -39,7 +36,7 @@ const HTTP_METHODS = new Set([
|
|
|
39
36
|
'options',
|
|
40
37
|
'trace',
|
|
41
38
|
]);
|
|
42
|
-
const toPosix$2 = (p) => p.split(
|
|
39
|
+
const toPosix$2 = (p) => p.split(sep).join('/');
|
|
43
40
|
const formatMaybe$1 = async (root, filePath, source) => {
|
|
44
41
|
try {
|
|
45
42
|
const prettier = (await import('prettier'));
|
|
@@ -51,10 +48,10 @@ const formatMaybe$1 = async (root, filePath, source) => {
|
|
|
51
48
|
}
|
|
52
49
|
};
|
|
53
50
|
const writeIfAbsent$1 = async (outFile, content) => {
|
|
54
|
-
if (
|
|
51
|
+
if (existsSync(outFile))
|
|
55
52
|
return { created: false };
|
|
56
|
-
await
|
|
57
|
-
await
|
|
53
|
+
await promises.mkdir(dirname(outFile), { recursive: true });
|
|
54
|
+
await promises.writeFile(outFile, content, 'utf8');
|
|
58
55
|
return { created: true };
|
|
59
56
|
};
|
|
60
57
|
/** True if a segment encodes a path param (:id | {id} | [id]). */
|
|
@@ -202,10 +199,10 @@ const runAdd = async (root, spec) => {
|
|
|
202
199
|
// Derive path template and param names for OpenAPI hints
|
|
203
200
|
const paramNames = baseParts.filter(isParamSeg).map(getParamName);
|
|
204
201
|
const pathTemplate = '/' + pathSegs.join('/');
|
|
205
|
-
const dir =
|
|
206
|
-
const lambdaPath =
|
|
207
|
-
const handlerPath =
|
|
208
|
-
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');
|
|
209
206
|
// Compute contents
|
|
210
207
|
const files = [];
|
|
211
208
|
if (isHttp) {
|
|
@@ -250,9 +247,9 @@ const runAdd = async (root, spec) => {
|
|
|
250
247
|
const formatted = await formatMaybe$1(root, f.path, f.content);
|
|
251
248
|
const { created: c } = await writeIfAbsent$1(f.path, formatted);
|
|
252
249
|
if (c)
|
|
253
|
-
created.push(
|
|
250
|
+
created.push(posix.normalize(f.path));
|
|
254
251
|
else
|
|
255
|
-
skipped.push(
|
|
252
|
+
skipped.push(posix.normalize(f.path));
|
|
256
253
|
}
|
|
257
254
|
return { created, skipped };
|
|
258
255
|
};
|
|
@@ -307,7 +304,7 @@ const launchOffline = async (root, opts) => {
|
|
|
307
304
|
const spawnOffline = async (root, cmd, verbose) => {
|
|
308
305
|
// Normalize child environment via get-dotenv when available; safe fallback otherwise.
|
|
309
306
|
const baseEnv = { ...process.env };
|
|
310
|
-
const childEnv =
|
|
307
|
+
const childEnv = buildSpawnEnv(baseEnv);
|
|
311
308
|
// Optional diagnostics to verify the child sees sane temp-related envs
|
|
312
309
|
if (verbose) {
|
|
313
310
|
const snap = {
|
|
@@ -320,7 +317,7 @@ const spawnOffline = async (root, cmd, verbose) => {
|
|
|
320
317
|
};
|
|
321
318
|
process.stdout.write(`[offline] env snapshot: ${JSON.stringify(snap)}\n`);
|
|
322
319
|
}
|
|
323
|
-
const child =
|
|
320
|
+
const child = spawn(cmd.cmd, cmd.args, {
|
|
324
321
|
cwd: root,
|
|
325
322
|
shell: cmd.shell,
|
|
326
323
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
@@ -364,7 +361,7 @@ const spawnOffline = async (root, cmd, verbose) => {
|
|
|
364
361
|
const findTsxCli = (root) => {
|
|
365
362
|
// Prefer invoking the JS entry to avoid shell .cmd quirks on Windows.
|
|
366
363
|
const js = path.resolve(root, 'node_modules', 'tsx', 'dist', 'cli.js');
|
|
367
|
-
if (
|
|
364
|
+
if (existsSync(js)) {
|
|
368
365
|
return {
|
|
369
366
|
cmd: process.execPath,
|
|
370
367
|
args: [js, 'app/config/openapi.ts'],
|
|
@@ -377,7 +374,7 @@ const findTsxCli = (root) => {
|
|
|
377
374
|
};
|
|
378
375
|
const findPrettierCli = (root) => {
|
|
379
376
|
const js = path.resolve(root, 'node_modules', 'prettier', 'bin', 'prettier.cjs');
|
|
380
|
-
if (!
|
|
377
|
+
if (!existsSync(js)) {
|
|
381
378
|
throw new Error(`prettier not found at ${js}. Install it as a devDependency.`);
|
|
382
379
|
}
|
|
383
380
|
return {
|
|
@@ -391,21 +388,21 @@ const runOpenapi = async (root, opts) => {
|
|
|
391
388
|
const outFile = path.resolve(root, 'app', 'generated', 'openapi.json');
|
|
392
389
|
let before;
|
|
393
390
|
try {
|
|
394
|
-
if (
|
|
395
|
-
before =
|
|
391
|
+
if (existsSync(outFile))
|
|
392
|
+
before = readFileSync(outFile, 'utf8');
|
|
396
393
|
}
|
|
397
394
|
catch {
|
|
398
395
|
// ignore read errors; treat as absent
|
|
399
396
|
before = undefined;
|
|
400
397
|
}
|
|
401
398
|
// Build a normalized child env via get-dotenv (static import).
|
|
402
|
-
const env =
|
|
399
|
+
const env = buildSpawnEnv({});
|
|
403
400
|
// Step 1: generate the OpenAPI spec.
|
|
404
401
|
const tsx = findTsxCli(root);
|
|
405
402
|
if (opts?.verbose) {
|
|
406
403
|
console.log(`[openapi] ${[tsx.cmd, ...tsx.args].join(' ')}`);
|
|
407
404
|
}
|
|
408
|
-
const genRes =
|
|
405
|
+
const genRes = spawnSync(tsx.cmd, tsx.args, {
|
|
409
406
|
cwd: root,
|
|
410
407
|
stdio: 'inherit',
|
|
411
408
|
shell: tsx.shell,
|
|
@@ -425,7 +422,7 @@ const runOpenapi = async (root, opts) => {
|
|
|
425
422
|
if (opts?.verbose) {
|
|
426
423
|
console.log(`[openapi] ${[prettier.cmd, ...prettier.args].join(' ')}`);
|
|
427
424
|
}
|
|
428
|
-
const fmtRes =
|
|
425
|
+
const fmtRes = spawnSync(prettier.cmd, prettier.args, {
|
|
429
426
|
cwd: root,
|
|
430
427
|
stdio: 'inherit',
|
|
431
428
|
shell: prettier.shell,
|
|
@@ -442,9 +439,9 @@ const runOpenapi = async (root, opts) => {
|
|
|
442
439
|
}
|
|
443
440
|
// Determine whether the file content changed
|
|
444
441
|
try {
|
|
445
|
-
if (!
|
|
442
|
+
if (!existsSync(outFile))
|
|
446
443
|
return before !== undefined; // deleted vs existed
|
|
447
|
-
const after =
|
|
444
|
+
const after = readFileSync(outFile, 'utf8');
|
|
448
445
|
return before === undefined ? true : after !== before;
|
|
449
446
|
}
|
|
450
447
|
catch {
|
|
@@ -464,7 +461,7 @@ const runOpenapi = async (root, opts) => {
|
|
|
464
461
|
*
|
|
465
462
|
* Idempotent and formatted (uses Prettier if available).
|
|
466
463
|
*/
|
|
467
|
-
const toPosix$1 = (p) => p.split(
|
|
464
|
+
const toPosix$1 = (p) => p.split(sep).join('/');
|
|
468
465
|
const withoutTsExt = (p) => p.endsWith('.ts') ? p.slice(0, -3) : p;
|
|
469
466
|
const HEADER = `/* AUTO-GENERATED by smoz register — DO NOT EDIT */
|
|
470
467
|
`;
|
|
@@ -481,21 +478,21 @@ const formatMaybe = async (root, filePath, source) => {
|
|
|
481
478
|
};
|
|
482
479
|
const writeIfChanged = async (outFile, content) => {
|
|
483
480
|
try {
|
|
484
|
-
const prev = await
|
|
481
|
+
const prev = await promises.readFile(outFile, 'utf8');
|
|
485
482
|
if (prev === content)
|
|
486
483
|
return false;
|
|
487
484
|
}
|
|
488
485
|
catch {
|
|
489
486
|
// file missing — proceed
|
|
490
487
|
}
|
|
491
|
-
await
|
|
492
|
-
await
|
|
488
|
+
await promises.mkdir(dirname(outFile), { recursive: true });
|
|
489
|
+
await promises.writeFile(outFile, content, 'utf8');
|
|
493
490
|
return true;
|
|
494
491
|
};
|
|
495
492
|
const walk$1 = async (dir, out = []) => {
|
|
496
|
-
const entries = await
|
|
493
|
+
const entries = await promises.readdir(dir, { withFileTypes: true });
|
|
497
494
|
for (const ent of entries) {
|
|
498
|
-
const p =
|
|
495
|
+
const p = join(dir, ent.name);
|
|
499
496
|
if (ent.isDirectory()) {
|
|
500
497
|
await walk$1(p, out);
|
|
501
498
|
}
|
|
@@ -506,13 +503,13 @@ const walk$1 = async (dir, out = []) => {
|
|
|
506
503
|
return out;
|
|
507
504
|
};
|
|
508
505
|
const collect = async (root) => {
|
|
509
|
-
const base =
|
|
506
|
+
const base = join(root, 'app', 'functions');
|
|
510
507
|
const buckets = { lambda: [], openapi: [], serverless: [] };
|
|
511
|
-
if (!
|
|
508
|
+
if (!existsSync(base))
|
|
512
509
|
return buckets;
|
|
513
510
|
const files = await walk$1(base);
|
|
514
511
|
for (const abs of files) {
|
|
515
|
-
const name = abs.split(
|
|
512
|
+
const name = abs.split(sep).pop() ?? '';
|
|
516
513
|
if (name === 'lambda.ts')
|
|
517
514
|
buckets.lambda.push(abs);
|
|
518
515
|
if (name === 'openapi.ts')
|
|
@@ -532,7 +529,7 @@ const collect = async (root) => {
|
|
|
532
529
|
* dynamic import in the inline server works reliably under tsx.
|
|
533
530
|
*/
|
|
534
531
|
const makeImports = (root, outDir, files) => files.map((abs) => {
|
|
535
|
-
const relFromOut = toPosix$1(
|
|
532
|
+
const relFromOut = toPosix$1(relative(outDir, abs));
|
|
536
533
|
const noExt = withoutTsExt(relFromOut);
|
|
537
534
|
const spec = noExt.startsWith('.') ? noExt : `./${noExt}`;
|
|
538
535
|
return `import '${spec}';`;
|
|
@@ -544,23 +541,23 @@ const buildFile = (imports) => {
|
|
|
544
541
|
};
|
|
545
542
|
const runRegister = async (root) => {
|
|
546
543
|
const buckets = await collect(root);
|
|
547
|
-
const outDir =
|
|
544
|
+
const outDir = join(root, 'app', 'generated');
|
|
548
545
|
const lambdaImports = makeImports(root, outDir, buckets.lambda);
|
|
549
546
|
const openapiImports = makeImports(root, outDir, buckets.openapi);
|
|
550
547
|
const serverlessImports = makeImports(root, outDir, buckets.serverless);
|
|
551
|
-
const fnsPath =
|
|
552
|
-
const oaiPath =
|
|
553
|
-
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');
|
|
554
551
|
const fns = await formatMaybe(root, fnsPath, buildFile(lambdaImports));
|
|
555
552
|
const oai = await formatMaybe(root, oaiPath, buildFile(openapiImports));
|
|
556
553
|
const srv = await formatMaybe(root, srvPath, buildFile(serverlessImports));
|
|
557
554
|
const wrote = [];
|
|
558
555
|
if (await writeIfChanged(fnsPath, fns))
|
|
559
|
-
wrote.push(
|
|
556
|
+
wrote.push(posix.normalize(fnsPath));
|
|
560
557
|
if (await writeIfChanged(oaiPath, oai))
|
|
561
|
-
wrote.push(
|
|
558
|
+
wrote.push(posix.normalize(oaiPath));
|
|
562
559
|
if (await writeIfChanged(srvPath, srv))
|
|
563
|
-
wrote.push(
|
|
560
|
+
wrote.push(posix.normalize(srvPath));
|
|
564
561
|
return { wrote };
|
|
565
562
|
};
|
|
566
563
|
|
|
@@ -592,8 +589,8 @@ const resolveStage = async (root, cliStage, verbose) => {
|
|
|
592
589
|
// getdotenv.config.json → plugins.smoz.stage
|
|
593
590
|
try {
|
|
594
591
|
const cfgPath = path.resolve(root, 'getdotenv.config.json');
|
|
595
|
-
if (
|
|
596
|
-
const raw =
|
|
592
|
+
if (existsSync(cfgPath)) {
|
|
593
|
+
const raw = readFileSync(cfgPath, 'utf8');
|
|
597
594
|
const parsed = JSON.parse(raw);
|
|
598
595
|
const fromCfg = parsed.plugins?.smoz?.stage;
|
|
599
596
|
if (typeof fromCfg === 'string' && fromCfg.trim().length > 0) {
|
|
@@ -618,7 +615,7 @@ const seedEnvForStage = async (root, stage, verbose) => {
|
|
|
618
615
|
// Best effort: import the app config to read declared env keys and concrete values.
|
|
619
616
|
// Preserve existing process.env values; only seed when unset.
|
|
620
617
|
try {
|
|
621
|
-
const appConfigUrl =
|
|
618
|
+
const appConfigUrl = pathToFileURL(path.resolve(root, 'app', 'config', 'app.config.ts')).href;
|
|
622
619
|
// Dynamically import the TS module under tsx
|
|
623
620
|
const mod = (await import(appConfigUrl));
|
|
624
621
|
const app = mod.app;
|
|
@@ -692,7 +689,7 @@ const resolveTsxCommand = (root, tsEntry) => {
|
|
|
692
689
|
shell: false,
|
|
693
690
|
};
|
|
694
691
|
}
|
|
695
|
-
const probe =
|
|
692
|
+
const probe = spawnSync(process.platform === 'win32' ? 'tsx.cmd' : 'tsx', ['--version'], { shell: true });
|
|
696
693
|
if (typeof probe.status === 'number' && probe.status === 0) {
|
|
697
694
|
return {
|
|
698
695
|
cmd: process.platform === 'win32' ? 'tsx.cmd' : 'tsx',
|
|
@@ -714,13 +711,13 @@ const launchInline = async (root, opts) => {
|
|
|
714
711
|
// Prefer __dirname when available (CJS), otherwise derive from import.meta.url (ESM).
|
|
715
712
|
const here = typeof __dirname === 'string'
|
|
716
713
|
? __dirname
|
|
717
|
-
: path.dirname(
|
|
718
|
-
const pkgRoot =
|
|
714
|
+
: path.dirname(fileURLToPath(import.meta.url));
|
|
715
|
+
const pkgRoot = packageDirectorySync({ cwd: here }) ?? process.cwd();
|
|
719
716
|
const { entry } = resolveInlineEntry(pkgRoot);
|
|
720
717
|
const makeTsx = () => {
|
|
721
718
|
const { cmd, args, shell } = resolveTsxCommand(root, entry);
|
|
722
719
|
return (async () => {
|
|
723
|
-
const env =
|
|
720
|
+
const env = buildSpawnEnv({
|
|
724
721
|
...process.env,
|
|
725
722
|
// Enable tsconfig paths for "@/..." during TS fallback.
|
|
726
723
|
TSX_TSCONFIG_PATHS: '1',
|
|
@@ -728,7 +725,7 @@ const launchInline = async (root, opts) => {
|
|
|
728
725
|
SMOZ_PORT: String(opts.port),
|
|
729
726
|
SMOZ_VERBOSE: opts.verbose ? '1' : '',
|
|
730
727
|
});
|
|
731
|
-
return
|
|
728
|
+
return spawn(cmd, args, {
|
|
732
729
|
cwd: root,
|
|
733
730
|
stdio: 'inherit',
|
|
734
731
|
shell,
|
|
@@ -894,16 +891,16 @@ const runDev = async (root, opts) => {
|
|
|
894
891
|
};
|
|
895
892
|
|
|
896
893
|
const writeIfAbsent = async (outFile, content) => {
|
|
897
|
-
if (
|
|
894
|
+
if (existsSync(outFile))
|
|
898
895
|
return { created: false };
|
|
899
|
-
await
|
|
900
|
-
await
|
|
896
|
+
await promises.mkdir(dirname(outFile), { recursive: true });
|
|
897
|
+
await promises.writeFile(outFile, content, 'utf8');
|
|
901
898
|
return { created: true };
|
|
902
899
|
};
|
|
903
900
|
const walk = async (dir, out = []) => {
|
|
904
|
-
const entries = await
|
|
901
|
+
const entries = await promises.readdir(dir, { withFileTypes: true });
|
|
905
902
|
for (const ent of entries) {
|
|
906
|
-
const p =
|
|
903
|
+
const p = join(dir, ent.name);
|
|
907
904
|
if (ent.isDirectory()) {
|
|
908
905
|
await walk(p, out);
|
|
909
906
|
}
|
|
@@ -915,7 +912,7 @@ const walk = async (dir, out = []) => {
|
|
|
915
912
|
};
|
|
916
913
|
const readJson = async (file) => {
|
|
917
914
|
try {
|
|
918
|
-
const data = await
|
|
915
|
+
const data = await promises.readFile(file, 'utf8');
|
|
919
916
|
return JSON.parse(data);
|
|
920
917
|
}
|
|
921
918
|
catch {
|
|
@@ -923,8 +920,8 @@ const readJson = async (file) => {
|
|
|
923
920
|
}
|
|
924
921
|
};
|
|
925
922
|
const writeJson = async (file, obj) => {
|
|
926
|
-
await
|
|
927
|
-
await
|
|
923
|
+
await promises.mkdir(dirname(file), { recursive: true });
|
|
924
|
+
await promises.writeFile(file, JSON.stringify(obj, null, 2), 'utf8');
|
|
928
925
|
};
|
|
929
926
|
|
|
930
927
|
const askConflict = async (rl, filePath) => {
|
|
@@ -950,25 +947,25 @@ const copyDirWithConflicts = async (srcDir, dstRoot, created, skipped, examples,
|
|
|
950
947
|
const files = await walk(srcDir);
|
|
951
948
|
let sticky;
|
|
952
949
|
for (const abs of files) {
|
|
953
|
-
const rel =
|
|
950
|
+
const rel = relative(srcDir, abs);
|
|
954
951
|
if (opts.exclude && opts.exclude(rel)) {
|
|
955
952
|
// Skip dev-only or excluded files silently.
|
|
956
953
|
continue;
|
|
957
954
|
}
|
|
958
|
-
const dest =
|
|
959
|
-
const data = await
|
|
960
|
-
if (!
|
|
955
|
+
const dest = resolve(dstRoot, rel);
|
|
956
|
+
const data = await promises.readFile(abs, 'utf8');
|
|
957
|
+
if (!existsSync(dest)) {
|
|
961
958
|
const { created: c } = await writeIfAbsent(dest, data);
|
|
962
959
|
if (c)
|
|
963
|
-
created.push(
|
|
960
|
+
created.push(posix.normalize(dest));
|
|
964
961
|
else
|
|
965
|
-
skipped.push(
|
|
962
|
+
skipped.push(posix.normalize(dest));
|
|
966
963
|
continue;
|
|
967
964
|
}
|
|
968
965
|
// Conflict flow
|
|
969
966
|
let decision = opts.conflict === 'ask' ? 'example' : opts.conflict;
|
|
970
967
|
if (opts.conflict === 'ask' && opts.rl && !sticky) {
|
|
971
|
-
const ans = await askConflict(opts.rl,
|
|
968
|
+
const ans = await askConflict(opts.rl, posix.normalize(dest));
|
|
972
969
|
if (ans === 'all-overwrite') {
|
|
973
970
|
sticky = 'overwrite';
|
|
974
971
|
}
|
|
@@ -985,32 +982,32 @@ const copyDirWithConflicts = async (srcDir, dstRoot, created, skipped, examples,
|
|
|
985
982
|
if (sticky)
|
|
986
983
|
decision = sticky;
|
|
987
984
|
if (decision === 'overwrite') {
|
|
988
|
-
await
|
|
989
|
-
await
|
|
990
|
-
created.push(
|
|
985
|
+
await promises.mkdir(dirname(dest), { recursive: true });
|
|
986
|
+
await promises.writeFile(dest, data, 'utf8');
|
|
987
|
+
created.push(posix.normalize(dest));
|
|
991
988
|
}
|
|
992
989
|
else if (decision === 'example') {
|
|
993
990
|
const ex = `${dest}.example`;
|
|
994
991
|
const { created: c } = await writeIfAbsent(ex, data);
|
|
995
992
|
if (c)
|
|
996
|
-
examples.push(
|
|
993
|
+
examples.push(posix.normalize(ex));
|
|
997
994
|
else
|
|
998
|
-
skipped.push(
|
|
995
|
+
skipped.push(posix.normalize(ex));
|
|
999
996
|
}
|
|
1000
997
|
else {
|
|
1001
|
-
skipped.push(
|
|
998
|
+
skipped.push(posix.normalize(dest));
|
|
1002
999
|
}
|
|
1003
1000
|
}
|
|
1004
1001
|
};
|
|
1005
1002
|
|
|
1006
1003
|
const detectPm = (root) => {
|
|
1007
|
-
if (
|
|
1004
|
+
if (existsSync(join(root, 'pnpm-lock.yaml')))
|
|
1008
1005
|
return 'pnpm';
|
|
1009
|
-
if (
|
|
1006
|
+
if (existsSync(join(root, 'yarn.lock')))
|
|
1010
1007
|
return 'yarn';
|
|
1011
|
-
if (
|
|
1008
|
+
if (existsSync(join(root, 'package-lock.json')))
|
|
1012
1009
|
return 'npm';
|
|
1013
|
-
if (
|
|
1010
|
+
if (existsSync(join(root, 'bun.lockb')))
|
|
1014
1011
|
return 'bun';
|
|
1015
1012
|
const ua = process.env.npm_config_user_agent ?? '';
|
|
1016
1013
|
if (ua.includes('pnpm'))
|
|
@@ -1029,7 +1026,7 @@ const runInstall = (root, pm) => {
|
|
|
1029
1026
|
const known = pm === 'pnpm' || pm === 'yarn' || pm === 'bun' || pm === 'npm';
|
|
1030
1027
|
if (!known)
|
|
1031
1028
|
return 'unknown-pm';
|
|
1032
|
-
const res =
|
|
1029
|
+
const res = spawnSync(pm, ['install'], {
|
|
1033
1030
|
stdio: 'inherit',
|
|
1034
1031
|
cwd: root,
|
|
1035
1032
|
shell: true,
|
|
@@ -1094,8 +1091,8 @@ const mergeAdditive = (target, source) => {
|
|
|
1094
1091
|
*/
|
|
1095
1092
|
const ensureToolkitDependency = async (targetPkg, templatesBase) => {
|
|
1096
1093
|
try {
|
|
1097
|
-
const toolkitRoot =
|
|
1098
|
-
const toolkitPkg = await readJson(
|
|
1094
|
+
const toolkitRoot = dirname(templatesBase);
|
|
1095
|
+
const toolkitPkg = await readJson(join(toolkitRoot, 'package.json'));
|
|
1099
1096
|
const verRaw = toolkitPkg?.version?.trim();
|
|
1100
1097
|
const depVersion = verRaw ? `^${verRaw}` : '^0.0.0';
|
|
1101
1098
|
const deps = targetPkg.dependencies ?? {};
|
|
@@ -1118,27 +1115,27 @@ const ensureToolkitDependency = async (targetPkg, templatesBase) => {
|
|
|
1118
1115
|
* a consuming app and from this repository.
|
|
1119
1116
|
+ */
|
|
1120
1117
|
const resolveTemplatesBase = () => {
|
|
1121
|
-
const here =
|
|
1122
|
-
const pkgRoot =
|
|
1123
|
-
return
|
|
1118
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
1119
|
+
const pkgRoot = packageDirectorySync({ cwd: here }) ?? process.cwd();
|
|
1120
|
+
return resolve(pkgRoot, 'templates');
|
|
1124
1121
|
};
|
|
1125
1122
|
const toPosix = (p) =>
|
|
1126
1123
|
// local helper for path presentation (not used for FS operations)
|
|
1127
1124
|
p.replace(/\\/g, '/');
|
|
1128
1125
|
|
|
1129
1126
|
const seedRegisterPlaceholders = async (root) => {
|
|
1130
|
-
const genDir =
|
|
1127
|
+
const genDir = join(root, 'app', 'generated');
|
|
1131
1128
|
const seeds = [
|
|
1132
1129
|
{
|
|
1133
|
-
path:
|
|
1130
|
+
path: join(genDir, 'register.functions.ts'),
|
|
1134
1131
|
content: '/* AUTO-GENERATED placeholder; will be rewritten by `smoz register` */\nexport {};\n',
|
|
1135
1132
|
},
|
|
1136
1133
|
{
|
|
1137
|
-
path:
|
|
1134
|
+
path: join(genDir, 'register.openapi.ts'),
|
|
1138
1135
|
content: '/* AUTO-GENERATED placeholder; will be rewritten by `smoz register` */\nexport {};\n',
|
|
1139
1136
|
},
|
|
1140
1137
|
{
|
|
1141
|
-
path:
|
|
1138
|
+
path: join(genDir, 'register.serverless.ts'),
|
|
1142
1139
|
content: '/* AUTO-GENERATED placeholder; will be rewritten by `smoz register` */\nexport {};\n',
|
|
1143
1140
|
},
|
|
1144
1141
|
];
|
|
@@ -1146,12 +1143,12 @@ const seedRegisterPlaceholders = async (root) => {
|
|
|
1146
1143
|
const skipped = [];
|
|
1147
1144
|
for (const s of seeds) {
|
|
1148
1145
|
const { created: c } = await writeIfAbsent(s.path, s.content);
|
|
1149
|
-
(c ? created : skipped).push(
|
|
1146
|
+
(c ? created : skipped).push(posix.normalize(s.path));
|
|
1150
1147
|
}
|
|
1151
1148
|
return { created, skipped };
|
|
1152
1149
|
};
|
|
1153
1150
|
|
|
1154
|
-
const toPosixSep = (p) => p.split(
|
|
1151
|
+
const toPosixSep = (p) => p.split(sep).join('/');
|
|
1155
1152
|
// Exclude dev-only type stubs (not meant for downstream apps).
|
|
1156
1153
|
const excludeDev = (rel) => {
|
|
1157
1154
|
const posixRel = rel.replace(/\\/g, '/');
|
|
@@ -1178,19 +1175,19 @@ const runInit = async (root, template = 'default', opts) => {
|
|
|
1178
1175
|
const optAll = opts ?? {};
|
|
1179
1176
|
const templatesBase = resolveTemplatesBase();
|
|
1180
1177
|
// Resolve template source: named template or filesystem path
|
|
1181
|
-
const templateIsPath =
|
|
1178
|
+
const templateIsPath = existsSync(template) && (await promises.stat(template)).isDirectory();
|
|
1182
1179
|
const srcBase = templateIsPath
|
|
1183
|
-
?
|
|
1184
|
-
:
|
|
1185
|
-
const projectBase =
|
|
1186
|
-
if (!
|
|
1180
|
+
? resolve(template)
|
|
1181
|
+
: resolve(templatesBase, template);
|
|
1182
|
+
const projectBase = resolve(templatesBase, 'project');
|
|
1183
|
+
if (!existsSync(srcBase)) {
|
|
1187
1184
|
throw new Error(`Template "${template}" not found (path or name). Tried: ${toPosix(srcBase)}.`);
|
|
1188
1185
|
}
|
|
1189
1186
|
// 1) Copy shared project boilerplate
|
|
1190
|
-
if (
|
|
1187
|
+
if (existsSync(projectBase)) {
|
|
1191
1188
|
const rl = optAll.yes === true
|
|
1192
1189
|
? undefined
|
|
1193
|
-
:
|
|
1190
|
+
: createInterface({ input: stdin, output: stdout, terminal: true });
|
|
1194
1191
|
let policy;
|
|
1195
1192
|
const c = optAll.conflict;
|
|
1196
1193
|
if (c === 'overwrite' || c === 'example' || c === 'skip' || c === 'ask')
|
|
@@ -1210,7 +1207,7 @@ const runInit = async (root, template = 'default', opts) => {
|
|
|
1210
1207
|
{
|
|
1211
1208
|
const rl = optAll.yes === true
|
|
1212
1209
|
? undefined
|
|
1213
|
-
:
|
|
1210
|
+
: createInterface({ input: stdin, output: stdout, terminal: true });
|
|
1214
1211
|
let policy;
|
|
1215
1212
|
const c = optAll.conflict;
|
|
1216
1213
|
if (c === 'overwrite' || c === 'example' || c === 'skip' || c === 'ask')
|
|
@@ -1228,21 +1225,21 @@ const runInit = async (root, template = 'default', opts) => {
|
|
|
1228
1225
|
}
|
|
1229
1226
|
// 2.5) Convert template 'gitignore' into a real '.gitignore'
|
|
1230
1227
|
try {
|
|
1231
|
-
const giSrc =
|
|
1232
|
-
const giDot =
|
|
1233
|
-
if (
|
|
1234
|
-
if (!
|
|
1235
|
-
await
|
|
1236
|
-
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));
|
|
1237
1234
|
}
|
|
1238
1235
|
else {
|
|
1239
|
-
const example =
|
|
1240
|
-
if (!
|
|
1241
|
-
const data = await
|
|
1242
|
-
await
|
|
1243
|
-
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));
|
|
1244
1241
|
}
|
|
1245
|
-
await
|
|
1242
|
+
await promises.rm(giSrc, { force: true });
|
|
1246
1243
|
}
|
|
1247
1244
|
}
|
|
1248
1245
|
}
|
|
@@ -1251,22 +1248,22 @@ const runInit = async (root, template = 'default', opts) => {
|
|
|
1251
1248
|
}
|
|
1252
1249
|
// 2.6) Convert 'tsconfig.downstream.json' into 'tsconfig.json'
|
|
1253
1250
|
try {
|
|
1254
|
-
const ds =
|
|
1255
|
-
const dst =
|
|
1256
|
-
if (
|
|
1257
|
-
if (!
|
|
1258
|
-
await
|
|
1259
|
-
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));
|
|
1260
1257
|
}
|
|
1261
1258
|
else {
|
|
1262
1259
|
// If a tsconfig already exists, write an example and remove the source
|
|
1263
|
-
const example =
|
|
1264
|
-
if (!
|
|
1265
|
-
const data = await
|
|
1266
|
-
await
|
|
1267
|
-
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));
|
|
1268
1265
|
}
|
|
1269
|
-
await
|
|
1266
|
+
await promises.rm(ds, { force: true });
|
|
1270
1267
|
}
|
|
1271
1268
|
}
|
|
1272
1269
|
}
|
|
@@ -1280,7 +1277,7 @@ const runInit = async (root, template = 'default', opts) => {
|
|
|
1280
1277
|
skipped.push(...res.skipped);
|
|
1281
1278
|
}
|
|
1282
1279
|
// 3) package.json presence (create when missing)
|
|
1283
|
-
const pkgPath =
|
|
1280
|
+
const pkgPath = join(root, 'package.json');
|
|
1284
1281
|
let pkg = await readJson(pkgPath);
|
|
1285
1282
|
if (!pkg) {
|
|
1286
1283
|
const name = toPosixSep(root).split('/').pop() ?? 'smoz-app';
|
|
@@ -1293,13 +1290,13 @@ const runInit = async (root, template = 'default', opts) => {
|
|
|
1293
1290
|
};
|
|
1294
1291
|
if (!optAll.dryRun)
|
|
1295
1292
|
await writeJson(pkgPath, pkg);
|
|
1296
|
-
created.push(
|
|
1293
|
+
created.push(posix.normalize(pkgPath));
|
|
1297
1294
|
}
|
|
1298
1295
|
// 4) Merge manifest additively (prefer template's embedded package.json)
|
|
1299
|
-
const templatePkgPath =
|
|
1300
|
-
const manifest =
|
|
1296
|
+
const templatePkgPath = resolve(srcBase, 'package.json');
|
|
1297
|
+
const manifest = existsSync(templatePkgPath)
|
|
1301
1298
|
? await readJson(templatePkgPath)
|
|
1302
|
-
: await readJson(
|
|
1299
|
+
: await readJson(resolve(templatesBase, '.manifests', `package.${template}.json`));
|
|
1303
1300
|
let pkgChanged = false;
|
|
1304
1301
|
if (manifest) {
|
|
1305
1302
|
const added = mergeAdditive(pkg, manifest);
|
|
@@ -1340,7 +1337,7 @@ const runInit = async (root, template = 'default', opts) => {
|
|
|
1340
1337
|
* Registers init/add/register/openapi/dev on a GetDotenvCli instance and
|
|
1341
1338
|
* delegates to existing implementations. No business logic here.
|
|
1342
1339
|
*/
|
|
1343
|
-
const repoRoot = () =>
|
|
1340
|
+
const repoRoot = () => packageDirectorySync() ?? process.cwd();
|
|
1344
1341
|
const setupSmozCommands = (cli) => {
|
|
1345
1342
|
// init
|
|
1346
1343
|
cli
|
|
@@ -1443,7 +1440,7 @@ const setupSmozCommands = (cli) => {
|
|
|
1443
1440
|
});
|
|
1444
1441
|
});
|
|
1445
1442
|
};
|
|
1446
|
-
const smozPlugin = () =>
|
|
1443
|
+
const smozPlugin = () => definePlugin({
|
|
1447
1444
|
ns: 'smoz',
|
|
1448
1445
|
setup: setupSmozCommands,
|
|
1449
1446
|
});
|
|
@@ -1456,7 +1453,7 @@ const smozPlugin = () => cliHost.definePlugin({
|
|
|
1456
1453
|
* - Install included plugins (cmd/batch/aws) and the SMOZ command plugin
|
|
1457
1454
|
* (init/add/register/openapi/dev).
|
|
1458
1455
|
*/
|
|
1459
|
-
const main =
|
|
1456
|
+
const main = createCli({
|
|
1460
1457
|
alias: 'smoz',
|
|
1461
1458
|
compose: (cli) => {
|
|
1462
1459
|
const setupResult = smozPlugin().setup(cli);
|
|
@@ -1464,12 +1461,12 @@ const main = cli.createCli({
|
|
|
1464
1461
|
throw new Error('smozPlugin root setup must remain synchronous.');
|
|
1465
1462
|
}
|
|
1466
1463
|
return cli
|
|
1467
|
-
.use(
|
|
1468
|
-
.use(
|
|
1464
|
+
.use(awsPlugin())
|
|
1465
|
+
.use(cmdPlugin({
|
|
1469
1466
|
asDefault: true,
|
|
1470
1467
|
optionAlias: '-c, --cmd <command...>',
|
|
1471
1468
|
}))
|
|
1472
|
-
.use(
|
|
1469
|
+
.use(batchPlugin());
|
|
1473
1470
|
},
|
|
1474
1471
|
});
|
|
1475
1472
|
void main();
|
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
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"
|
|
@@ -183,7 +183,7 @@
|
|
|
183
183
|
"templates:lint": "eslint --fix -c templates/default/eslint.config.ts \"templates/default/**/*.{ts,tsx,js,jsx}\" \"templates/default/eslint.config.ts\""
|
|
184
184
|
},
|
|
185
185
|
"type": "module",
|
|
186
|
-
"version": "0.2.
|
|
186
|
+
"version": "0.2.15",
|
|
187
187
|
"volta": {
|
|
188
188
|
"node": "22.19.0"
|
|
189
189
|
}
|