@karmaniverous/smoz 0.2.6 → 0.2.7
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 +153 -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,146 @@ 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 launchInline = async (root, opts) => {
|
|
640
|
+
// Resolve the installed smoz package root robustly (works from compiled CLI and tsx ESM):
|
|
641
|
+
// Prefer __dirname when available (CJS), otherwise derive from import.meta.url (ESM).
|
|
642
|
+
const here = typeof __dirname === 'string'
|
|
643
|
+
? __dirname
|
|
644
|
+
: 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))));
|
|
645
|
+
const pkgRoot = packageDirectory.packageDirectorySync({ cwd: here }) ?? process.cwd();
|
|
646
|
+
const tsEntry = path.resolve(pkgRoot, 'src', 'cli', 'local', 'inline.server', 'index.ts');
|
|
647
|
+
const makeTsx = () => {
|
|
648
|
+
const { cmd, args, shell } = resolveTsxCommand(root, tsEntry);
|
|
649
|
+
return node_child_process.spawn(cmd, args, {
|
|
650
|
+
cwd: root,
|
|
651
|
+
stdio: 'inherit',
|
|
652
|
+
shell,
|
|
653
|
+
// Enable tsconfig paths for "@/..." during TS fallback.
|
|
654
|
+
env: {
|
|
655
|
+
...process.env,
|
|
656
|
+
TSX_TSCONFIG_PATHS: '1',
|
|
657
|
+
SMOZ_STAGE: opts.stage,
|
|
658
|
+
SMOZ_PORT: String(opts.port),
|
|
659
|
+
SMOZ_VERBOSE: opts.verbose ? '1' : '',
|
|
660
|
+
},
|
|
661
|
+
});
|
|
662
|
+
};
|
|
663
|
+
let child = makeTsx();
|
|
664
|
+
const close = async () => new Promise((resolve) => {
|
|
665
|
+
// If the process has already exited (exitCode set), resolve immediately.
|
|
666
|
+
if (child.exitCode !== null) {
|
|
667
|
+
resolve();
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
child.once('exit', () => {
|
|
671
|
+
resolve();
|
|
672
|
+
});
|
|
673
|
+
child.kill('SIGTERM');
|
|
674
|
+
setTimeout(() => {
|
|
675
|
+
resolve();
|
|
676
|
+
}, 1500);
|
|
677
|
+
});
|
|
678
|
+
const restart = async () => {
|
|
679
|
+
await close();
|
|
680
|
+
child = makeTsx();
|
|
681
|
+
};
|
|
682
|
+
return { close, restart };
|
|
683
|
+
};
|
|
684
|
+
|
|
536
685
|
const runDev = async (root, opts) => {
|
|
537
686
|
const verbose = !!opts.verbose;
|
|
538
687
|
const stage = typeof opts.stage === 'string'
|
|
@@ -547,7 +696,7 @@ const runDev = async (root, opts) => {
|
|
|
547
696
|
console.warn('[dev] env seeding warning:', e.message);
|
|
548
697
|
}
|
|
549
698
|
const modeInitial = opts.local;
|
|
550
|
-
|
|
699
|
+
const mode = modeInitial;
|
|
551
700
|
const port = typeof opts.port === 'number' ? opts.port : 0;
|
|
552
701
|
if (verbose) {
|
|
553
702
|
console.log(`[dev] options: register=${String(opts.register)} ` +
|
|
@@ -633,16 +782,8 @@ const runDev = async (root, opts) => {
|
|
|
633
782
|
offline = await launchOffline(root, { stage, port, verbose });
|
|
634
783
|
}
|
|
635
784
|
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
|
-
}
|
|
785
|
+
// Hard error on failure; no fallback to offline.
|
|
786
|
+
inlineChild = await launchInline(root, { stage, port, verbose });
|
|
646
787
|
}
|
|
647
788
|
// Watch sources
|
|
648
789
|
const globs = [
|
|
@@ -677,154 +818,6 @@ const runDev = async (root, opts) => {
|
|
|
677
818
|
});
|
|
678
819
|
});
|
|
679
820
|
};
|
|
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
821
|
|
|
829
822
|
const writeIfAbsent = async (outFile, content) => {
|
|
830
823
|
if (fs.existsSync(outFile))
|
package/package.json
CHANGED