@karmaniverous/smoz 0.2.6 → 0.2.8
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 +160 -160
- package/package.json +1 -1
package/dist/cli/index.cjs
CHANGED
|
@@ -5,10 +5,10 @@ var fs = require('node:fs');
|
|
|
5
5
|
var path = require('node:path');
|
|
6
6
|
var commander = require('commander');
|
|
7
7
|
var packageDirectory = require('package-directory');
|
|
8
|
-
var node_url = require('node:url');
|
|
9
8
|
var chokidar = require('chokidar');
|
|
10
9
|
var node_child_process = require('node:child_process');
|
|
11
10
|
var os = require('node:os');
|
|
11
|
+
var node_url = require('node:url');
|
|
12
12
|
var node_process = require('node:process');
|
|
13
13
|
var promises = require('node:readline/promises');
|
|
14
14
|
|
|
@@ -348,10 +348,19 @@ const spawnOffline = (root, cmd, verbose) => {
|
|
|
348
348
|
env: baseEnv,
|
|
349
349
|
});
|
|
350
350
|
const prefix = '[offline] ';
|
|
351
|
+
let announced = false;
|
|
351
352
|
const emit = (buf) => {
|
|
352
353
|
const text = buf.toString('utf8');
|
|
353
354
|
if (verbose)
|
|
354
355
|
process.stdout.write(prefix + text);
|
|
356
|
+
// Detect the actual listening URL and announce it once to avoid "0" confusion.
|
|
357
|
+
const m = text.match(/listening on (https?:\/\/(?:localhost|127\.0\.0\.1):\d+)/i);
|
|
358
|
+
const url = m?.[1];
|
|
359
|
+
if (url && !announced) {
|
|
360
|
+
announced = true;
|
|
361
|
+
// Print a concise, canonical URL line.
|
|
362
|
+
process.stdout.write(`[dev] offline: ${url}\n`);
|
|
363
|
+
}
|
|
355
364
|
};
|
|
356
365
|
const emitErr = (buf) => {
|
|
357
366
|
const text = buf.toString('utf8');
|
|
@@ -533,6 +542,153 @@ const runRegister = async (root) => {
|
|
|
533
542
|
return { wrote };
|
|
534
543
|
};
|
|
535
544
|
|
|
545
|
+
const inferDefaultStage = (root, verbose) => {
|
|
546
|
+
// Prefer “dev”; explicit --stage overrides remain available.
|
|
547
|
+
if (verbose)
|
|
548
|
+
console.log('[dev] inferring stage: dev (explicit --stage overrides)');
|
|
549
|
+
return 'dev';
|
|
550
|
+
};
|
|
551
|
+
const seedEnvForStage = async (root, stage, verbose) => {
|
|
552
|
+
// Best effort: import the app config to read declared env keys and concrete values.
|
|
553
|
+
// Preserve existing process.env values; only seed when unset.
|
|
554
|
+
try {
|
|
555
|
+
const appConfigUrl = node_url.pathToFileURL(path.resolve(root, 'app', 'config', 'app.config.ts')).href;
|
|
556
|
+
// Dynamically import the TS module under tsx
|
|
557
|
+
const mod = (await import(appConfigUrl));
|
|
558
|
+
const app = mod.app;
|
|
559
|
+
const stages = mod.stages;
|
|
560
|
+
const globalKeys = Array.isArray(app?.global?.envKeys)
|
|
561
|
+
? app.global.envKeys
|
|
562
|
+
: [];
|
|
563
|
+
const stageKeys = Array.isArray(app?.stage?.envKeys)
|
|
564
|
+
? app.stage.envKeys
|
|
565
|
+
: [];
|
|
566
|
+
const globalParams = (stages?.default).params ?? {};
|
|
567
|
+
const stageParams = (stages?.[stage]).params ?? {};
|
|
568
|
+
const seedPair = (key, from) => {
|
|
569
|
+
if (key in process.env)
|
|
570
|
+
return;
|
|
571
|
+
const val = from[key];
|
|
572
|
+
if (val === undefined)
|
|
573
|
+
return;
|
|
574
|
+
if (typeof val === 'string') {
|
|
575
|
+
process.env[key] = val;
|
|
576
|
+
if (verbose)
|
|
577
|
+
console.log(`[dev] env: ${key}=${val}`);
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
if (typeof val === 'number' || typeof val === 'boolean') {
|
|
581
|
+
const v = String(val);
|
|
582
|
+
process.env[key] = v;
|
|
583
|
+
if (verbose)
|
|
584
|
+
console.log(`[dev] env: ${key}=${v}`);
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
// Non-primitive; skip to avoid [object Object] surprise.
|
|
588
|
+
if (verbose)
|
|
589
|
+
console.log(`[dev] env: skip ${key} (non-primitive)`);
|
|
590
|
+
};
|
|
591
|
+
for (const k of globalKeys) {
|
|
592
|
+
if (typeof k === 'string') {
|
|
593
|
+
seedPair(k, globalParams);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
for (const k of stageKeys) {
|
|
597
|
+
if (typeof k === 'string') {
|
|
598
|
+
seedPair(k, stageParams);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
// Ensure STAGE itself is present as a last resort
|
|
602
|
+
if (!process.env.STAGE) {
|
|
603
|
+
process.env.STAGE = stage;
|
|
604
|
+
if (verbose)
|
|
605
|
+
console.log(`[dev] env: STAGE=${stage}`);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
catch {
|
|
609
|
+
// Fallback: seed STAGE only
|
|
610
|
+
if (!process.env.STAGE) {
|
|
611
|
+
process.env.STAGE = stage;
|
|
612
|
+
if (verbose)
|
|
613
|
+
console.log(`[dev] env: STAGE=${stage}`);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
};
|
|
617
|
+
|
|
618
|
+
const resolveTsxCommand = (root, tsEntry) => {
|
|
619
|
+
const localCli = path.resolve(root, 'node_modules', 'tsx', 'dist', 'cli.js');
|
|
620
|
+
if (fs.existsSync(localCli)) {
|
|
621
|
+
// Normalize to POSIX separators for cross-platform comparisons (tests)
|
|
622
|
+
const localCliPosix = localCli.split(path.sep).join('/');
|
|
623
|
+
return {
|
|
624
|
+
cmd: process.execPath,
|
|
625
|
+
args: [localCliPosix, tsEntry],
|
|
626
|
+
shell: false,
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
const probe = node_child_process.spawnSync(process.platform === 'win32' ? 'tsx.cmd' : 'tsx', ['--version'], { shell: true });
|
|
630
|
+
if (typeof probe.status === 'number' && probe.status === 0) {
|
|
631
|
+
return {
|
|
632
|
+
cmd: process.platform === 'win32' ? 'tsx.cmd' : 'tsx',
|
|
633
|
+
args: [tsEntry],
|
|
634
|
+
shell: true,
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
throw new Error('Inline requires tsx. Install it with "npm i -D tsx", or run "smoz dev -l offline".');
|
|
638
|
+
};
|
|
639
|
+
const resolveInlineEntry = (pkgRoot) => {
|
|
640
|
+
const compiled = path.resolve(pkgRoot, 'dist', 'mjs', 'cli', 'inline-server.js');
|
|
641
|
+
if (fs.existsSync(compiled))
|
|
642
|
+
return { entry: compiled, kind: 'compiled' };
|
|
643
|
+
const tsPath = path.resolve(pkgRoot, 'src', 'cli', 'local', 'inline.server', 'index.ts');
|
|
644
|
+
return { entry: tsPath, kind: 'ts' };
|
|
645
|
+
};
|
|
646
|
+
const launchInline = async (root, opts) => {
|
|
647
|
+
// Resolve the installed smoz package root robustly (works from compiled CLI and tsx ESM):
|
|
648
|
+
// Prefer __dirname when available (CJS), otherwise derive from import.meta.url (ESM).
|
|
649
|
+
const here = typeof __dirname === 'string'
|
|
650
|
+
? __dirname
|
|
651
|
+
: path.dirname(node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))));
|
|
652
|
+
const pkgRoot = packageDirectory.packageDirectorySync({ cwd: here }) ?? process.cwd();
|
|
653
|
+
const { entry } = resolveInlineEntry(pkgRoot);
|
|
654
|
+
const makeTsx = () => {
|
|
655
|
+
const { cmd, args, shell } = resolveTsxCommand(root, entry);
|
|
656
|
+
return node_child_process.spawn(cmd, args, {
|
|
657
|
+
cwd: root,
|
|
658
|
+
stdio: 'inherit',
|
|
659
|
+
shell,
|
|
660
|
+
// Enable tsconfig paths for "@/..." during TS fallback.
|
|
661
|
+
env: {
|
|
662
|
+
...process.env,
|
|
663
|
+
TSX_TSCONFIG_PATHS: '1',
|
|
664
|
+
SMOZ_STAGE: opts.stage,
|
|
665
|
+
SMOZ_PORT: String(opts.port),
|
|
666
|
+
SMOZ_VERBOSE: opts.verbose ? '1' : '',
|
|
667
|
+
},
|
|
668
|
+
});
|
|
669
|
+
};
|
|
670
|
+
let child = makeTsx();
|
|
671
|
+
const close = async () => new Promise((resolve) => {
|
|
672
|
+
// If the process has already exited (exitCode set), resolve immediately.
|
|
673
|
+
if (child.exitCode !== null) {
|
|
674
|
+
resolve();
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
child.once('exit', () => {
|
|
678
|
+
resolve();
|
|
679
|
+
});
|
|
680
|
+
child.kill('SIGTERM');
|
|
681
|
+
setTimeout(() => {
|
|
682
|
+
resolve();
|
|
683
|
+
}, 1500);
|
|
684
|
+
});
|
|
685
|
+
const restart = async () => {
|
|
686
|
+
await close();
|
|
687
|
+
child = makeTsx();
|
|
688
|
+
};
|
|
689
|
+
return { close, restart };
|
|
690
|
+
};
|
|
691
|
+
|
|
536
692
|
const runDev = async (root, opts) => {
|
|
537
693
|
const verbose = !!opts.verbose;
|
|
538
694
|
const stage = typeof opts.stage === 'string'
|
|
@@ -547,7 +703,7 @@ const runDev = async (root, opts) => {
|
|
|
547
703
|
console.warn('[dev] env seeding warning:', e.message);
|
|
548
704
|
}
|
|
549
705
|
const modeInitial = opts.local;
|
|
550
|
-
|
|
706
|
+
const mode = modeInitial;
|
|
551
707
|
const port = typeof opts.port === 'number' ? opts.port : 0;
|
|
552
708
|
if (verbose) {
|
|
553
709
|
console.log(`[dev] options: register=${String(opts.register)} ` +
|
|
@@ -633,16 +789,8 @@ const runDev = async (root, opts) => {
|
|
|
633
789
|
offline = await launchOffline(root, { stage, port, verbose });
|
|
634
790
|
}
|
|
635
791
|
else if (mode === 'inline') {
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
}
|
|
639
|
-
catch (e) {
|
|
640
|
-
const msg = e.message;
|
|
641
|
-
console.warn('[dev] inline unavailable:', msg);
|
|
642
|
-
console.warn('[dev] Falling back to serverless-offline.');
|
|
643
|
-
mode = 'offline';
|
|
644
|
-
offline = await launchOffline(root, { stage, port, verbose });
|
|
645
|
-
}
|
|
792
|
+
// Hard error on failure; no fallback to offline.
|
|
793
|
+
inlineChild = await launchInline(root, { stage, port, verbose });
|
|
646
794
|
}
|
|
647
795
|
// Watch sources
|
|
648
796
|
const globs = [
|
|
@@ -677,154 +825,6 @@ const runDev = async (root, opts) => {
|
|
|
677
825
|
});
|
|
678
826
|
});
|
|
679
827
|
};
|
|
680
|
-
const inferDefaultStage = (root, verbose) => {
|
|
681
|
-
// Prefer “dev”; if app.config.ts is available and we can inspect it cheaply later, expand behavior.
|
|
682
|
-
// For now, return 'dev' (explicit selection via --stage remains available).
|
|
683
|
-
if (verbose)
|
|
684
|
-
console.log('[dev] inferring stage: dev (explicit --stage overrides)');
|
|
685
|
-
return 'dev';
|
|
686
|
-
};
|
|
687
|
-
const seedEnvForStage = async (root, stage, verbose) => {
|
|
688
|
-
// Best effort: import the app config to read declared env keys and concrete values.
|
|
689
|
-
// Preserve existing process.env values; only seed when unset.
|
|
690
|
-
try {
|
|
691
|
-
const appConfigUrl = node_url.pathToFileURL(path.resolve(root, 'app', 'config', 'app.config.ts')).href;
|
|
692
|
-
// Dynamically import the TS module under tsx
|
|
693
|
-
const mod = (await import(appConfigUrl));
|
|
694
|
-
const app = mod.app;
|
|
695
|
-
const stages = mod.stages;
|
|
696
|
-
const globalKeys = Array.isArray(app?.global?.envKeys)
|
|
697
|
-
? app.global.envKeys
|
|
698
|
-
: [];
|
|
699
|
-
const stageKeys = Array.isArray(app?.stage?.envKeys)
|
|
700
|
-
? app.stage.envKeys
|
|
701
|
-
: [];
|
|
702
|
-
const globalParams = (stages?.default).params ?? {};
|
|
703
|
-
const stageParams = (stages?.[stage]).params ?? {};
|
|
704
|
-
const seedPair = (key, from) => {
|
|
705
|
-
if (key in process.env)
|
|
706
|
-
return;
|
|
707
|
-
const val = from[key];
|
|
708
|
-
if (val === undefined)
|
|
709
|
-
return;
|
|
710
|
-
if (typeof val === 'string') {
|
|
711
|
-
process.env[key] = val;
|
|
712
|
-
if (verbose)
|
|
713
|
-
console.log(`[dev] env: ${key}=${val}`);
|
|
714
|
-
return;
|
|
715
|
-
}
|
|
716
|
-
if (typeof val === 'number' || typeof val === 'boolean') {
|
|
717
|
-
const v = String(val);
|
|
718
|
-
process.env[key] = v;
|
|
719
|
-
if (verbose)
|
|
720
|
-
console.log(`[dev] env: ${key}=${v}`);
|
|
721
|
-
return;
|
|
722
|
-
}
|
|
723
|
-
// Non-primitive; skip to avoid [object Object] surprise.
|
|
724
|
-
if (verbose)
|
|
725
|
-
console.log(`[dev] env: skip ${key} (non-primitive)`);
|
|
726
|
-
};
|
|
727
|
-
for (const k of globalKeys) {
|
|
728
|
-
if (typeof k === 'string') {
|
|
729
|
-
seedPair(k, globalParams);
|
|
730
|
-
}
|
|
731
|
-
}
|
|
732
|
-
for (const k of stageKeys) {
|
|
733
|
-
if (typeof k === 'string') {
|
|
734
|
-
seedPair(k, stageParams);
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
// Ensure STAGE itself is present as a last resort
|
|
738
|
-
if (!process.env.STAGE) {
|
|
739
|
-
process.env.STAGE = stage;
|
|
740
|
-
if (verbose)
|
|
741
|
-
console.log(`[dev] env: STAGE=${stage}`);
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
catch {
|
|
745
|
-
// Fallback: seed STAGE only
|
|
746
|
-
if (!process.env.STAGE) {
|
|
747
|
-
process.env.STAGE = stage;
|
|
748
|
-
if (verbose)
|
|
749
|
-
console.log(`[dev] env: STAGE=${stage}`);
|
|
750
|
-
}
|
|
751
|
-
}
|
|
752
|
-
};
|
|
753
|
-
// Inline local runner: spawn tsx to run a TS server script.
|
|
754
|
-
const launchInline = async (root, opts) => {
|
|
755
|
-
const { spawn, spawnSync } = await import('node:child_process');
|
|
756
|
-
const path = await import('node:path');
|
|
757
|
-
const fs = await import('node:fs');
|
|
758
|
-
// Resolve packaged inline server from the project root so this works
|
|
759
|
-
// both when running the built CLI and via tsx on sources.
|
|
760
|
-
const entry = path.resolve(root, 'dist', 'mjs', 'cli', 'inline-server.js');
|
|
761
|
-
if (!fs.existsSync(entry)) {
|
|
762
|
-
throw new Error('inline server entry missing in package (dist/mjs/cli/inline-server.js)');
|
|
763
|
-
}
|
|
764
|
-
// Prefer project-local tsx; otherwise probe PATH for tsx availability
|
|
765
|
-
const tsxCli = path.resolve(root, 'node_modules', 'tsx', 'dist', 'cli.js');
|
|
766
|
-
let cmd;
|
|
767
|
-
let args;
|
|
768
|
-
let useShell = false;
|
|
769
|
-
let tsxAvailable = false;
|
|
770
|
-
if (fs.existsSync(tsxCli)) {
|
|
771
|
-
tsxAvailable = true;
|
|
772
|
-
cmd = process.execPath;
|
|
773
|
-
// Pass the TSX CLI script followed by our entry. On Windows, avoid
|
|
774
|
-
// putting unknown flags before the script to prevent Node from treating
|
|
775
|
-
// them as its own options. Enable tsconfig-paths via env only.
|
|
776
|
-
args = [tsxCli, entry];
|
|
777
|
-
}
|
|
778
|
-
else {
|
|
779
|
-
// Probe PATH: tsx --version
|
|
780
|
-
const probe = spawnSync(process.platform === 'win32' ? 'tsx.cmd' : 'tsx', ['--version'], { shell: true });
|
|
781
|
-
if (typeof probe.status === 'number' && probe.status === 0)
|
|
782
|
-
tsxAvailable = true;
|
|
783
|
-
cmd = process.platform === 'win32' ? 'tsx.cmd' : 'tsx';
|
|
784
|
-
// Invoke tsx from PATH with just the script entry; rely on
|
|
785
|
-
// TSX_TSCONFIG_PATHS=1 in the environment for path alias resolution.
|
|
786
|
-
args = [entry];
|
|
787
|
-
useShell = true;
|
|
788
|
-
}
|
|
789
|
-
if (!tsxAvailable) {
|
|
790
|
-
throw new Error('tsx not found (required for inline). Install "tsx" as a devDependency or use --local offline.');
|
|
791
|
-
}
|
|
792
|
-
const spawnChild = () => spawn(cmd, args, {
|
|
793
|
-
cwd: root,
|
|
794
|
-
stdio: 'inherit',
|
|
795
|
-
shell: useShell,
|
|
796
|
-
// Enable tsconfig paths resolution for "@/..." via environment;
|
|
797
|
-
// avoids passing CLI flags that can confuse Node on Windows.
|
|
798
|
-
// Keep additional SMOZ_* env for server configuration.
|
|
799
|
-
env: {
|
|
800
|
-
...process.env,
|
|
801
|
-
TSX_TSCONFIG_PATHS: '1',
|
|
802
|
-
SMOZ_STAGE: opts.stage,
|
|
803
|
-
SMOZ_PORT: String(opts.port),
|
|
804
|
-
SMOZ_VERBOSE: opts.verbose ? '1' : '',
|
|
805
|
-
},
|
|
806
|
-
});
|
|
807
|
-
let child = spawnChild();
|
|
808
|
-
const close = async () => new Promise((resolve) => {
|
|
809
|
-
// If the process has already exited (exitCode set), resolve immediately.
|
|
810
|
-
if (child.exitCode !== null) {
|
|
811
|
-
resolve();
|
|
812
|
-
return;
|
|
813
|
-
}
|
|
814
|
-
child.once('exit', () => {
|
|
815
|
-
resolve();
|
|
816
|
-
});
|
|
817
|
-
child.kill('SIGTERM');
|
|
818
|
-
setTimeout(() => {
|
|
819
|
-
resolve();
|
|
820
|
-
}, 1500);
|
|
821
|
-
});
|
|
822
|
-
const restart = async () => {
|
|
823
|
-
await close();
|
|
824
|
-
child = spawnChild();
|
|
825
|
-
};
|
|
826
|
-
return { close, restart };
|
|
827
|
-
};
|
|
828
828
|
|
|
829
829
|
const writeIfAbsent = async (outFile, content) => {
|
|
830
830
|
if (fs.existsSync(outFile))
|
package/package.json
CHANGED