mikrojs 0.0.7 → 0.0.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/cliWrapper.js +13 -0
- package/dist/cli/cliWrapper.js.map +1 -1
- package/dist/cli/commands/flash.js +1 -1
- package/dist/cli/commands/sim/dev.d.ts +12 -0
- package/dist/cli/commands/sim/dev.d.ts.map +1 -1
- package/dist/cli/commands/sim/dev.js +265 -139
- package/dist/cli/commands/sim/dev.js.map +1 -1
- package/dist/cli/commands/sim/profile.js +3 -1
- package/dist/cli/commands/sim/profile.js.map +1 -1
- package/dist/cli/commands/sim/test.js +1 -1
- package/dist/cli/commands/sim/test.js.map +1 -1
- package/dist/cli/commands/sim.d.ts +8 -0
- package/dist/cli/commands/sim.d.ts.map +1 -1
- package/dist/cli/commands/test.js +1 -1
- package/dist/cli/commands/test.js.map +1 -1
- package/dist/cli/lib/esptool.js +2 -2
- package/dist/cli/lib/firmware.d.ts.map +1 -1
- package/dist/cli/lib/firmware.js +56 -29
- package/dist/cli/lib/firmware.js.map +1 -1
- package/dist/cli/lib/serial/ReplConsole.js +1 -1
- package/dist/cli/lib/serial/ReplConsole.js.map +1 -1
- package/dist/cli/lib/serial/devSession.d.ts +4 -0
- package/dist/cli/lib/serial/devSession.d.ts.map +1 -1
- package/dist/cli/lib/serial/devSession.js +3 -3
- package/dist/cli/lib/serial/devSession.js.map +1 -1
- package/dist/cli/lib/serial/replStateMachine.d.ts.map +1 -1
- package/dist/cli/lib/serial/replStateMachine.js +9 -2
- package/dist/cli/lib/serial/replStateMachine.js.map +1 -1
- package/dist/cli/lib/simPid.d.ts.map +1 -1
- package/dist/cli/lib/simPid.js +12 -4
- package/dist/cli/lib/simPid.js.map +1 -1
- package/dist/simulator/builtins/ble.d.ts.map +1 -1
- package/dist/simulator/builtins/ble.js +11 -10
- package/dist/simulator/builtins/ble.js.map +1 -1
- package/dist/simulator/builtins/http.d.ts.map +1 -1
- package/dist/simulator/builtins/http.js +73 -15
- package/dist/simulator/builtins/http.js.map +1 -1
- package/dist/simulator/builtins/i2c.d.ts.map +1 -1
- package/dist/simulator/builtins/i2c.js +6 -5
- package/dist/simulator/builtins/i2c.js.map +1 -1
- package/dist/simulator/builtins/kv.d.ts.map +1 -1
- package/dist/simulator/builtins/kv.js +2 -1
- package/dist/simulator/builtins/kv.js.map +1 -1
- package/dist/simulator/builtins/neopixel.d.ts.map +1 -1
- package/dist/simulator/builtins/neopixel.js +7 -5
- package/dist/simulator/builtins/neopixel.js.map +1 -1
- package/dist/simulator/builtins/nvs-kv.d.ts.map +1 -1
- package/dist/simulator/builtins/nvs-kv.js +37 -2
- package/dist/simulator/builtins/nvs-kv.js.map +1 -1
- package/dist/simulator/builtins/pin.d.ts.map +1 -1
- package/dist/simulator/builtins/pin.js +5 -4
- package/dist/simulator/builtins/pin.js.map +1 -1
- package/dist/simulator/builtins/pwm.d.ts.map +1 -1
- package/dist/simulator/builtins/pwm.js +6 -4
- package/dist/simulator/builtins/pwm.js.map +1 -1
- package/dist/simulator/builtins/sleep.d.ts.map +1 -1
- package/dist/simulator/builtins/sleep.js +7 -6
- package/dist/simulator/builtins/sleep.js.map +1 -1
- package/dist/simulator/builtins/sntp.d.ts.map +1 -1
- package/dist/simulator/builtins/sntp.js +3 -1
- package/dist/simulator/builtins/sntp.js.map +1 -1
- package/dist/simulator/builtins/spi.d.ts.map +1 -1
- package/dist/simulator/builtins/spi.js +5 -4
- package/dist/simulator/builtins/spi.js.map +1 -1
- package/dist/simulator/builtins/uart.d.ts.map +1 -1
- package/dist/simulator/builtins/uart.js +6 -4
- package/dist/simulator/builtins/uart.js.map +1 -1
- package/dist/simulator/builtins/wifi.d.ts.map +1 -1
- package/dist/simulator/builtins/wifi.js +20 -19
- package/dist/simulator/builtins/wifi.js.map +1 -1
- package/dist/simulator/devRunner.d.ts.map +1 -1
- package/dist/simulator/devRunner.js +61 -0
- package/dist/simulator/devRunner.js.map +1 -1
- package/dist/simulator/simProcess.js +46 -52
- package/dist/simulator/simProcess.js.map +1 -1
- package/package.json +4 -4
package/dist/cli/cliWrapper.js
CHANGED
|
@@ -4,6 +4,9 @@ import * as path from 'node:path';
|
|
|
4
4
|
const cliPath = path.join(import.meta.dirname, '/cli.js');
|
|
5
5
|
function run() {
|
|
6
6
|
const child = fork(cliPath, process.argv.slice(2));
|
|
7
|
+
// Track whether we're tearing down to start a fresh child (reload) so we
|
|
8
|
+
// can suppress the wrapper's own exit on the dying child.
|
|
9
|
+
let restarting = false;
|
|
7
10
|
const onMessage = (message) => {
|
|
8
11
|
if (message === 'exit') {
|
|
9
12
|
exit('SIGTERM', false);
|
|
@@ -13,6 +16,7 @@ function run() {
|
|
|
13
16
|
}
|
|
14
17
|
};
|
|
15
18
|
function exit(signal, restart) {
|
|
19
|
+
restarting = restart;
|
|
16
20
|
child.kill('SIGTERM');
|
|
17
21
|
child.removeAllListeners('message');
|
|
18
22
|
// eslint-disable-next-line no-console
|
|
@@ -21,6 +25,15 @@ function run() {
|
|
|
21
25
|
run();
|
|
22
26
|
}
|
|
23
27
|
}
|
|
28
|
+
// Forward the child's exit code to the wrapper. Without this, a child that
|
|
29
|
+
// calls process.exit(1) (e.g. on a fatal CLI error like a missing env file)
|
|
30
|
+
// would leave the wrapper exiting 0, masking the failure from shell scripts
|
|
31
|
+
// and CI. Suppressed during reload so the new child's lifetime drives exit.
|
|
32
|
+
child.on('exit', (code, signal) => {
|
|
33
|
+
if (restarting)
|
|
34
|
+
return;
|
|
35
|
+
process.exit(code ?? (signal ? 1 : 0));
|
|
36
|
+
});
|
|
24
37
|
child.on('message', onMessage);
|
|
25
38
|
}
|
|
26
39
|
run();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cliWrapper.js","sourceRoot":"","sources":["../../src/cli/cliWrapper.ts"],"names":[],"mappings":";AACA,OAAO,EAAC,IAAI,EAAC,MAAM,oBAAoB,CAAA;AACvC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAA;AAEjC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,CAAA;AAEzD,SAAS,GAAG;IACV,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"cliWrapper.js","sourceRoot":"","sources":["../../src/cli/cliWrapper.ts"],"names":[],"mappings":";AACA,OAAO,EAAC,IAAI,EAAC,MAAM,oBAAoB,CAAA;AACvC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAA;AAEjC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,CAAA;AAEzD,SAAS,GAAG;IACV,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;IAClD,yEAAyE;IACzE,0DAA0D;IAC1D,IAAI,UAAU,GAAG,KAAK,CAAA;IAEtB,MAAM,SAAS,GAAG,CAAC,OAAe,EAAE,EAAE;QACpC,IAAI,OAAO,KAAK,MAAM,EAAE,CAAC;YACvB,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,CAAA;QACxB,CAAC;QACD,IAAI,OAAO,KAAK,QAAQ,EAAE,CAAC;YACzB,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAA;QACtB,CAAC;IACH,CAAC,CAAA;IACD,SAAS,IAAI,CAAC,MAA4B,EAAE,OAAgB;QAC1D,UAAU,GAAG,OAAO,CAAA;QACpB,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;QACrB,KAAK,CAAC,kBAAkB,CAAC,SAAS,CAAC,CAAA;QACnC,sCAAsC;QACtC,OAAO,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,cAAc,EAAE,CAAC,CAAA;QACnF,IAAI,OAAO,EAAE,CAAC;YACZ,GAAG,EAAE,CAAA;QACP,CAAC;IACH,CAAC;IACD,2EAA2E;IAC3E,4EAA4E;IAC5E,4EAA4E;IAC5E,4EAA4E;IAC5E,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE;QAChC,IAAI,UAAU;YAAE,OAAM;QACtB,OAAO,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IACxC,CAAC,CAAC,CAAA;IACF,KAAK,CAAC,EAAE,CAAC,SAAS,EAAE,SAAS,CAAC,CAAA;AAChC,CAAC;AAED,GAAG,EAAE,CAAA"}
|
|
@@ -157,7 +157,7 @@ export default function FlashCmd(props) {
|
|
|
157
157
|
if (!hasPrebuiltFirmware(resolvedChip)) {
|
|
158
158
|
throw new Error(`No bundled firmware for ${resolvedChip}. ` +
|
|
159
159
|
`Build a custom firmware locally and flash with --build-dir, ` +
|
|
160
|
-
`or fetch a CI artifact with --from=mikrojs/mikrojs
|
|
160
|
+
`or fetch a CI artifact with --from=mikrojs/mikrojs@<sha>.`);
|
|
161
161
|
}
|
|
162
162
|
const flasherArgs = await readFlasherArgs(prebuiltFirmwareDir(resolvedChip));
|
|
163
163
|
setInitState({ status: 'ready', flasherArgs, esptoolPath });
|
|
@@ -1,35 +1,47 @@
|
|
|
1
1
|
export declare const args: import("@optique/core").Parser<"sync", {
|
|
2
2
|
readonly subcommand: "dev";
|
|
3
3
|
readonly entry: string | undefined;
|
|
4
|
+
readonly forceDeploy: true | undefined;
|
|
4
5
|
readonly noMinify: true | undefined;
|
|
5
6
|
readonly minifier: string | undefined;
|
|
6
7
|
readonly minifyLevel: string | undefined;
|
|
7
8
|
readonly noBytecode: true | undefined;
|
|
9
|
+
readonly noHooks: true | undefined;
|
|
10
|
+
readonly logLevel: string | undefined;
|
|
8
11
|
readonly env: string | undefined;
|
|
9
12
|
readonly secrets: string | undefined;
|
|
10
13
|
readonly noEnvFile: true | undefined;
|
|
14
|
+
readonly noWatch: true | undefined;
|
|
11
15
|
readonly agent: true | undefined;
|
|
12
16
|
}, ["matched", string] | ["parsing", {
|
|
13
17
|
readonly subcommand: "dev";
|
|
14
18
|
readonly entry: [import("@optique/core").ValueParserResult<string> | undefined] | undefined;
|
|
19
|
+
readonly forceDeploy: [import("@optique/core").ValueParserResult<true> | undefined] | undefined;
|
|
15
20
|
readonly noMinify: [import("@optique/core").ValueParserResult<true> | undefined] | undefined;
|
|
16
21
|
readonly minifier: [import("@optique/core").ValueParserResult<string> | undefined] | undefined;
|
|
17
22
|
readonly minifyLevel: [import("@optique/core").ValueParserResult<string> | undefined] | undefined;
|
|
18
23
|
readonly noBytecode: [import("@optique/core").ValueParserResult<true> | undefined] | undefined;
|
|
24
|
+
readonly noHooks: [import("@optique/core").ValueParserResult<true> | undefined] | undefined;
|
|
25
|
+
readonly logLevel: [import("@optique/core").ValueParserResult<string> | undefined] | undefined;
|
|
19
26
|
readonly env: [import("@optique/core").ValueParserResult<string> | undefined] | undefined;
|
|
20
27
|
readonly secrets: [import("@optique/core").ValueParserResult<string> | undefined] | undefined;
|
|
21
28
|
readonly noEnvFile: [import("@optique/core").ValueParserResult<true> | undefined] | undefined;
|
|
29
|
+
readonly noWatch: [import("@optique/core").ValueParserResult<true> | undefined] | undefined;
|
|
22
30
|
readonly agent: [import("@optique/core").ValueParserResult<true> | undefined] | undefined;
|
|
23
31
|
}] | undefined>;
|
|
24
32
|
interface DevConfig {
|
|
25
33
|
entry?: string;
|
|
34
|
+
forceDeploy?: boolean;
|
|
26
35
|
noMinify?: boolean;
|
|
27
36
|
minifier?: string;
|
|
28
37
|
minifyLevel?: string;
|
|
29
38
|
noBytecode?: boolean;
|
|
39
|
+
noHooks?: boolean;
|
|
40
|
+
logLevel?: string;
|
|
30
41
|
env?: string;
|
|
31
42
|
secrets?: string;
|
|
32
43
|
noEnvFile?: boolean;
|
|
44
|
+
noWatch?: boolean;
|
|
33
45
|
agent?: boolean;
|
|
34
46
|
}
|
|
35
47
|
export declare function run(config: DevConfig): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/sim/dev.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/sim/dev.tsx"],"names":[],"mappings":"AA8BA,eAAO,MAAM,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;eAqDhB,CAAA;AAED,UAAU,SAAS;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AA4BD,wBAAsB,GAAG,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAgJ1D;AAQD,MAAM,CAAC,OAAO,UAAU,MAAM,CAAC,KAAK,EAAE;IAAC,IAAI,EAAE,SAAS,CAAA;CAAC,2CAEtD"}
|
|
@@ -6,24 +6,22 @@ import { argument, flag, option } from '@optique/core/primitives';
|
|
|
6
6
|
import { string } from '@optique/core/valueparser';
|
|
7
7
|
import { path } from '@optique/run';
|
|
8
8
|
import spinners from 'cli-spinners';
|
|
9
|
-
import { Text } from 'ink';
|
|
10
|
-
import
|
|
11
|
-
import {
|
|
12
|
-
import { agentEmit, agentError, agentResult, createAgentStdinReader } from '../../lib/agent.js';
|
|
13
|
-
import { build } from '../../lib/build.js';
|
|
14
|
-
import { collectFiles, loadEnvFiles } from '../../lib/deploy.js';
|
|
9
|
+
import { Text, useApp, useInput } from 'ink';
|
|
10
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
11
|
+
import { filter, firstValueFrom, map, Subject } from 'rxjs';
|
|
12
|
+
import { agentEmit, agentError, agentResult, createAgentStdinReader, forwardSessionToAgent, } from '../../lib/agent.js';
|
|
15
13
|
import { openSim } from '../../lib/openSim.js';
|
|
16
|
-
import { parseMinifier, parseMinifyLevel } from '../../lib/parseMinifier.js';
|
|
17
|
-
import { getMikroDir, resolveProjectRoot } from '../../lib/projectRoot.js';
|
|
14
|
+
import { parseLogLevel, parseMinifier, parseMinifyLevel } from '../../lib/parseMinifier.js';
|
|
18
15
|
import { resolveEntry } from '../../lib/resolveEntry.js';
|
|
19
|
-
import {
|
|
16
|
+
import { createDevSession } from '../../lib/serial/devSession.js';
|
|
17
|
+
import { InkReplConsole } from '../../lib/serial/InkReplConsole.js';
|
|
20
18
|
import { createRepl } from '../../lib/serial/replStateMachine.js';
|
|
21
19
|
import { SimAlreadyRunningError } from '../../lib/simPid.js';
|
|
22
20
|
import { Spinner } from '../../lib/Spinner.js';
|
|
23
|
-
import { createWatcher } from '../../lib/watcher.js';
|
|
24
21
|
export const args = command('dev', object({
|
|
25
22
|
subcommand: constant('dev'),
|
|
26
23
|
entry: optional(argument(path({ metavar: 'ENTRY', mustExist: true, type: 'file' }))),
|
|
24
|
+
forceDeploy: optional(flag('--force-deploy', { description: message `Force full deploy, ignoring cached checksums` })),
|
|
27
25
|
noMinify: optional(flag('--no-minify', { description: message `Skip minification` })),
|
|
28
26
|
minifier: optional(option('--minifier', string({ metavar: 'NAME' }), {
|
|
29
27
|
description: message `Minifier: esbuild, terser, or swc (default: esbuild)`,
|
|
@@ -32,6 +30,12 @@ export const args = command('dev', object({
|
|
|
32
30
|
description: message `Minify level: default or max`,
|
|
33
31
|
})),
|
|
34
32
|
noBytecode: optional(flag('--no-bytecode', { description: message `Skip bytecode compilation` })),
|
|
33
|
+
noHooks: optional(flag('--no-hooks', {
|
|
34
|
+
description: message `Skip mikrojs.predeploy hooks from package.json`,
|
|
35
|
+
})),
|
|
36
|
+
logLevel: optional(option('--loglevel', string({ metavar: 'LEVEL' }), {
|
|
37
|
+
description: message `Log level: none, error, warn, info, debug. Console calls below this level are eliminated at build time.`,
|
|
38
|
+
})),
|
|
35
39
|
env: optional(option('--env', string({ metavar: 'FILE' }), {
|
|
36
40
|
description: message `Path to .env file with environment variables`,
|
|
37
41
|
})),
|
|
@@ -41,20 +45,43 @@ export const args = command('dev', object({
|
|
|
41
45
|
noEnvFile: optional(flag('--no-env-file', {
|
|
42
46
|
description: message `Skip auto-loading of .env and .env.simulator from the project root`,
|
|
43
47
|
})),
|
|
48
|
+
noWatch: optional(flag('--no-watch', {
|
|
49
|
+
description: message `Deploy once, then drop into the REPL without watching for file changes`,
|
|
50
|
+
})),
|
|
44
51
|
agent: optional(flag('--agent', { description: message `NDJSON agent protocol over stdio` })),
|
|
45
52
|
}), { description: message `Watch + build + deploy to simulator with REPL` });
|
|
53
|
+
/**
|
|
54
|
+
* Resolve the user's CLI args into the shape `createDevSession` expects.
|
|
55
|
+
* `mode: 'simulator'` drives `MIKRO_ENV` and the auto-loaded `.env.simulator`
|
|
56
|
+
* file, distinguishing sim runs from `mikro dev` (which defaults to
|
|
57
|
+
* `'development'`).
|
|
58
|
+
*/
|
|
59
|
+
function devSessionOptions(config) {
|
|
60
|
+
return {
|
|
61
|
+
entry: resolveEntry(config.entry),
|
|
62
|
+
forceDeploy: config.forceDeploy === true,
|
|
63
|
+
minify: !config.noMinify,
|
|
64
|
+
bytecode: !config.noBytecode,
|
|
65
|
+
watch: config.noWatch !== true,
|
|
66
|
+
noHooks: config.noHooks === true,
|
|
67
|
+
minifier: parseMinifier(config.minifier),
|
|
68
|
+
minifyLevel: parseMinifyLevel(config.minifyLevel),
|
|
69
|
+
logLevel: parseLogLevel(config.logLevel),
|
|
70
|
+
envFile: config.env,
|
|
71
|
+
secretsFile: config.secrets,
|
|
72
|
+
noEnvFile: config.noEnvFile === true,
|
|
73
|
+
mode: 'simulator',
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
// ── Agent mode ─────────────────────────────────────────────────────
|
|
46
77
|
export async function run(config) {
|
|
47
|
-
const entry = resolveEntry(config.entry);
|
|
48
|
-
const buildDir = pathlib.join(getMikroDir(), 'build');
|
|
49
|
-
const watchDir = process.cwd();
|
|
50
|
-
const minify = !config.noMinify;
|
|
51
|
-
const minifier = parseMinifier(config.minifier);
|
|
52
|
-
const minifyLevel = parseMinifyLevel(config.minifyLevel);
|
|
53
|
-
const bytecode = !config.noBytecode;
|
|
54
78
|
let session;
|
|
55
79
|
let transport;
|
|
56
80
|
try {
|
|
57
|
-
const conn = await openSim({
|
|
81
|
+
const conn = await openSim({
|
|
82
|
+
claim: true,
|
|
83
|
+
startDir: pathlib.dirname(resolveEntry(config.entry)),
|
|
84
|
+
});
|
|
58
85
|
session = conn.session;
|
|
59
86
|
transport = conn.transport;
|
|
60
87
|
}
|
|
@@ -65,59 +92,97 @@ export async function run(config) {
|
|
|
65
92
|
}
|
|
66
93
|
throw err;
|
|
67
94
|
}
|
|
68
|
-
session
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
95
|
+
forwardSessionToAgent(session);
|
|
96
|
+
const opts = devSessionOptions(config);
|
|
97
|
+
const deploys$ = new Subject();
|
|
98
|
+
const dev = createDevSession({
|
|
99
|
+
session,
|
|
100
|
+
entry: opts.entry,
|
|
101
|
+
forceDeploy: opts.forceDeploy,
|
|
102
|
+
minify: opts.minify,
|
|
103
|
+
bytecode: opts.bytecode,
|
|
104
|
+
watch: opts.watch,
|
|
105
|
+
noHooks: opts.noHooks,
|
|
106
|
+
minifier: opts.minifier,
|
|
107
|
+
minifyLevel: opts.minifyLevel,
|
|
108
|
+
logLevel: opts.logLevel,
|
|
109
|
+
envFile: opts.envFile,
|
|
110
|
+
secretsFile: opts.secretsFile,
|
|
111
|
+
noEnvFile: opts.noEnvFile,
|
|
112
|
+
externalDeploys$: deploys$.asObservable(),
|
|
113
|
+
mode: opts.mode,
|
|
114
|
+
});
|
|
115
|
+
// Translate DevSessionState transitions into agent NDJSON events. Mirrors
|
|
116
|
+
// mikro dev's run() so consumers (CI, scripts, AI agents) see the same
|
|
117
|
+
// event stream regardless of target.
|
|
118
|
+
const idleStatus = opts.watch ? 'watching' : 'idle';
|
|
119
|
+
let stateSub = dev.state$.subscribe((state) => {
|
|
120
|
+
switch (state.status.type) {
|
|
121
|
+
case 'checking':
|
|
122
|
+
agentEmit({ type: 'status', status: 'checking', command: state.status.command });
|
|
123
|
+
break;
|
|
124
|
+
case 'building':
|
|
125
|
+
case 'rebuilding':
|
|
126
|
+
agentEmit({ type: 'status', status: 'building' });
|
|
72
127
|
break;
|
|
73
|
-
case '
|
|
74
|
-
|
|
128
|
+
case 'deploying': {
|
|
129
|
+
const event = state.status.event;
|
|
130
|
+
if (event.type === 'uploading' || event.type === 'checking') {
|
|
131
|
+
agentEmit({
|
|
132
|
+
type: `deploy_${event.type}`,
|
|
133
|
+
file: event.file,
|
|
134
|
+
index: event.index,
|
|
135
|
+
total: event.total,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
else if (event.type === 'connecting') {
|
|
139
|
+
agentEmit({ type: 'status', status: 'deploying' });
|
|
140
|
+
}
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
case 'watching':
|
|
144
|
+
agentEmit({ type: 'status', status: idleStatus });
|
|
75
145
|
break;
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
agentEmit({ type: event.type, text: event.text });
|
|
146
|
+
case 'error':
|
|
147
|
+
agentEmit({ type: 'status', status: 'error', error: state.status.message });
|
|
79
148
|
break;
|
|
80
149
|
}
|
|
81
150
|
});
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
151
|
+
const dispose = () => {
|
|
152
|
+
stateSub?.unsubscribe();
|
|
153
|
+
stateSub = null;
|
|
154
|
+
dev.close();
|
|
155
|
+
try {
|
|
156
|
+
session.close();
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
// already closed
|
|
160
|
+
}
|
|
161
|
+
try {
|
|
162
|
+
transport.close();
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
// already closed
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
process.on('exit', dispose);
|
|
169
|
+
process.on('SIGINT', () => process.exit(0));
|
|
170
|
+
process.on('SIGTERM', () => process.exit(0));
|
|
171
|
+
// Await the first non-building state to detect initial-deploy failure.
|
|
172
|
+
// Match mikro dev's contract: error on the initial path is fatal, exits
|
|
173
|
+
// with non-zero so CI/scripts see a failure.
|
|
102
174
|
try {
|
|
103
|
-
await
|
|
175
|
+
await firstValueFrom(dev.state$.pipe(filter((s) => s.status.type === 'watching' || s.status.type === 'error'), map((s) => {
|
|
176
|
+
if (s.status.type === 'error')
|
|
177
|
+
throw new Error(s.status.message);
|
|
178
|
+
return s;
|
|
179
|
+
})));
|
|
104
180
|
}
|
|
105
181
|
catch (err) {
|
|
106
182
|
const msg = err instanceof Error ? err.message : String(err);
|
|
107
183
|
agentError('sim dev', msg, { fix: 'Check entry file and try again' });
|
|
108
184
|
process.exit(1);
|
|
109
185
|
}
|
|
110
|
-
process.on('exit', () => session.close());
|
|
111
|
-
process.on('SIGINT', () => process.exit(0));
|
|
112
|
-
process.on('SIGTERM', () => process.exit(0));
|
|
113
|
-
const { changes$, close: closeWatcher } = createWatcher(watchDir);
|
|
114
|
-
changes$
|
|
115
|
-
.pipe(exhaustMap(() => defer(() => doBuildAndDeploy()).pipe(catchError((err) => {
|
|
116
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
117
|
-
agentEmit({ type: 'status', status: 'error', error: msg });
|
|
118
|
-
return EMPTY;
|
|
119
|
-
}))))
|
|
120
|
-
.subscribe();
|
|
121
186
|
for await (const cmd of createAgentStdinReader()) {
|
|
122
187
|
switch (cmd.type) {
|
|
123
188
|
case 'eval':
|
|
@@ -135,120 +200,181 @@ export async function run(config) {
|
|
|
135
200
|
case 'restart':
|
|
136
201
|
session.restart();
|
|
137
202
|
break;
|
|
203
|
+
case 'deploy':
|
|
204
|
+
deploys$.next({ force: cmd.force === true });
|
|
205
|
+
break;
|
|
138
206
|
case 'exit':
|
|
139
207
|
session.exit();
|
|
140
|
-
|
|
141
|
-
closeWatcher();
|
|
142
|
-
transport.close();
|
|
208
|
+
dispose();
|
|
143
209
|
agentResult('sim dev', null, []);
|
|
144
210
|
process.exit(0);
|
|
145
211
|
}
|
|
146
212
|
}
|
|
213
|
+
// stdin closed without explicit 'exit' — clean shutdown.
|
|
214
|
+
dispose();
|
|
215
|
+
agentResult('sim dev', null, []);
|
|
216
|
+
process.exit(0);
|
|
147
217
|
}
|
|
148
218
|
export default function SimDev(props) {
|
|
149
|
-
|
|
150
|
-
const entry = resolveEntry(cfg.entry);
|
|
151
|
-
return (_jsx(SimDevMode, { entry: entry, minify: !cfg.noMinify, bytecode: !cfg.noBytecode, minifier: parseMinifier(cfg.minifier), minifyLevel: parseMinifyLevel(cfg.minifyLevel), envFile: cfg.env, secretsFile: cfg.secrets, noEnvFile: cfg.noEnvFile === true }));
|
|
219
|
+
return _jsx(SimDevMode, { config: props.args });
|
|
152
220
|
}
|
|
221
|
+
/**
|
|
222
|
+
* TUI counterpart of `run()`. Connects the simulator session, instantiates
|
|
223
|
+
* `createDevSession` (the same state machine `mikro dev` uses), and renders
|
|
224
|
+
* the REPL once the initial deploy completes.
|
|
225
|
+
*
|
|
226
|
+
* Initial-setup errors (sim subprocess fails to start, first build/deploy
|
|
227
|
+
* fails) are fatal — there's nothing for the watcher to fall back to.
|
|
228
|
+
* Watch-mode rebuild errors stay in the TUI so the user can fix the file
|
|
229
|
+
* and let the watcher retry.
|
|
230
|
+
*/
|
|
153
231
|
function SimDevMode(props) {
|
|
154
|
-
const {
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
232
|
+
const { config } = props;
|
|
233
|
+
const opts = devSessionOptions(config);
|
|
234
|
+
// We mount the REPL UI as soon as the sim session+repl handle exist, BEFORE
|
|
235
|
+
// the first deploy completes. This matches the device-side InkReplMode flow
|
|
236
|
+
// and is required for correctness: the REPL state machine's awaitReady$
|
|
237
|
+
// / connectionTimeout$ only fire when `repl.state$` has a subscriber. If
|
|
238
|
+
// the REPL UI doesn't mount until after the first deploy, the state
|
|
239
|
+
// machine never gets to poll CMD_HELLO during the deploy window and
|
|
240
|
+
// ends up timing out the moment it does finally mount (because by then
|
|
241
|
+
// multiple restarts have happened).
|
|
242
|
+
const [conn, setConn] = useState(null);
|
|
243
|
+
const [error, setError] = useState(null);
|
|
244
|
+
const fatal = useCallback((err) => {
|
|
245
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
246
|
+
process.stderr.write(`Error: ${msg}\n`);
|
|
247
|
+
process.exit(1);
|
|
248
|
+
}, []);
|
|
249
|
+
const handleEnd = useCallback(() => {
|
|
250
|
+
process.stdout.write('\x1b[<u');
|
|
251
|
+
process.exit(0);
|
|
252
|
+
}, []);
|
|
253
|
+
// Ink's `exitOnCtrlC` is disabled at the cli.ts render call; until the REPL
|
|
254
|
+
// is mounted (or while an error screen is showing), nothing else handles
|
|
255
|
+
// input — without this, ctrl-c is a black hole. Matches DevicePicker /
|
|
256
|
+
// InkReplMode / deploy convention (ctrl-c or ctrl-q).
|
|
257
|
+
const { exit: exitInk } = useApp();
|
|
258
|
+
useInput((input, key) => {
|
|
259
|
+
if (key.ctrl && (input === 'c' || input === 'q')) {
|
|
260
|
+
exitInk();
|
|
261
|
+
process.exit(error ? 1 : 0);
|
|
262
|
+
}
|
|
263
|
+
}, { isActive: !conn });
|
|
264
|
+
useEffect(() => {
|
|
161
265
|
let disposed = false;
|
|
162
266
|
let cleanup = null;
|
|
163
267
|
void (async () => {
|
|
164
268
|
let session;
|
|
165
269
|
let transport;
|
|
166
270
|
try {
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
271
|
+
const handle = await openSim({
|
|
272
|
+
claim: true,
|
|
273
|
+
startDir: pathlib.dirname(opts.entry),
|
|
274
|
+
});
|
|
275
|
+
session = handle.session;
|
|
276
|
+
transport = handle.transport;
|
|
170
277
|
}
|
|
171
278
|
catch (err) {
|
|
172
279
|
if (!disposed)
|
|
173
|
-
|
|
280
|
+
fatal(err);
|
|
174
281
|
return;
|
|
175
282
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
cwd: resolveProjectRoot(),
|
|
186
|
-
mode: 'simulator',
|
|
187
|
-
envFile,
|
|
188
|
-
secretsFile,
|
|
189
|
-
noEnvFile,
|
|
190
|
-
})),
|
|
191
|
-
];
|
|
192
|
-
await lastValueFrom(build(entry, buildDir, { minify, bytecode, minifier, minifyLevel }), {
|
|
193
|
-
defaultValue: undefined,
|
|
194
|
-
});
|
|
195
|
-
if (disposed)
|
|
196
|
-
return;
|
|
197
|
-
const allFiles = await collectFiles(buildDir);
|
|
198
|
-
const handle = createRepl({
|
|
199
|
-
session,
|
|
200
|
-
port: 'simulator',
|
|
201
|
-
onEnd: () => process.exit(0),
|
|
202
|
-
});
|
|
203
|
-
handle.state$.subscribe();
|
|
204
|
-
await lastValueFrom(session.deploy({ files: allFiles, envVars, restart: true }));
|
|
205
|
-
if (disposed)
|
|
206
|
-
return;
|
|
207
|
-
replRef.current = handle;
|
|
208
|
-
if (!disposed)
|
|
209
|
-
setRepl({ handle, config: session.config });
|
|
283
|
+
if (disposed) {
|
|
284
|
+
try {
|
|
285
|
+
session.close();
|
|
286
|
+
transport.close();
|
|
287
|
+
}
|
|
288
|
+
catch {
|
|
289
|
+
// already closed
|
|
290
|
+
}
|
|
291
|
+
return;
|
|
210
292
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
293
|
+
const repl = createRepl({
|
|
294
|
+
session,
|
|
295
|
+
port: 'simulator',
|
|
296
|
+
onEnd: handleEnd,
|
|
297
|
+
deployEnabled: true,
|
|
298
|
+
});
|
|
299
|
+
const dev = createDevSession({
|
|
300
|
+
session,
|
|
301
|
+
repl,
|
|
302
|
+
entry: opts.entry,
|
|
303
|
+
forceDeploy: opts.forceDeploy,
|
|
304
|
+
minify: opts.minify,
|
|
305
|
+
bytecode: opts.bytecode,
|
|
306
|
+
watch: opts.watch,
|
|
307
|
+
noHooks: opts.noHooks,
|
|
308
|
+
minifier: opts.minifier,
|
|
309
|
+
minifyLevel: opts.minifyLevel,
|
|
310
|
+
logLevel: opts.logLevel,
|
|
311
|
+
envFile: opts.envFile,
|
|
312
|
+
secretsFile: opts.secretsFile,
|
|
313
|
+
noEnvFile: opts.noEnvFile,
|
|
314
|
+
mode: opts.mode,
|
|
315
|
+
});
|
|
316
|
+
// Mount the REPL UI immediately so the state machine starts polling
|
|
317
|
+
// CMD_HELLO and the `connectionTimeout$` can be cancelled by the first
|
|
318
|
+
// MSG_READY. createDevSession's run is kept alive by the stateSub
|
|
319
|
+
// subscription below; rendering the REPL is purely a UI concern.
|
|
320
|
+
setConn({ session, transport, repl, dev });
|
|
321
|
+
const stateSub = dev.state$.subscribe((state) => {
|
|
322
|
+
if (state.status.type === 'error') {
|
|
323
|
+
// Surface in TUI; the watcher can recover by re-running on next
|
|
324
|
+
// file change, so we keep the session alive.
|
|
325
|
+
if (!disposed)
|
|
326
|
+
setError(new Error(state.status.message));
|
|
327
|
+
}
|
|
328
|
+
else if (state.status.type === 'watching' || state.status.type === 'deploying') {
|
|
329
|
+
if (!disposed)
|
|
330
|
+
setError(null);
|
|
331
|
+
}
|
|
214
332
|
});
|
|
215
|
-
const { changes$, close } = createWatcher(watchDir);
|
|
216
|
-
const sub = changes$
|
|
217
|
-
.pipe(exhaustMap(() => defer(() => buildAndDeploy()).pipe(catchError((err) => {
|
|
218
|
-
if (!disposed)
|
|
219
|
-
setError(err instanceof Error ? err : new Error(String(err)));
|
|
220
|
-
return EMPTY;
|
|
221
|
-
}))))
|
|
222
|
-
.subscribe();
|
|
223
333
|
cleanup = () => {
|
|
224
|
-
|
|
225
|
-
close();
|
|
226
|
-
|
|
227
|
-
|
|
334
|
+
stateSub.unsubscribe();
|
|
335
|
+
dev.close();
|
|
336
|
+
try {
|
|
337
|
+
session.close();
|
|
338
|
+
}
|
|
339
|
+
catch {
|
|
340
|
+
// already closed
|
|
341
|
+
}
|
|
342
|
+
try {
|
|
343
|
+
transport.close();
|
|
344
|
+
}
|
|
345
|
+
catch {
|
|
346
|
+
// already closed
|
|
347
|
+
}
|
|
228
348
|
};
|
|
229
349
|
})();
|
|
230
350
|
return () => {
|
|
231
351
|
disposed = true;
|
|
232
352
|
cleanup?.();
|
|
233
353
|
};
|
|
354
|
+
// opts is recomputed each render but its values are stable for a given
|
|
355
|
+
// config (which itself is stable for a session). Deps captured so React
|
|
356
|
+
// doesn't complain; effective re-runs only happen on real config change.
|
|
357
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
234
358
|
}, [
|
|
235
|
-
entry,
|
|
236
|
-
|
|
237
|
-
minify,
|
|
238
|
-
bytecode,
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
359
|
+
opts.entry,
|
|
360
|
+
opts.forceDeploy,
|
|
361
|
+
opts.minify,
|
|
362
|
+
opts.bytecode,
|
|
363
|
+
opts.watch,
|
|
364
|
+
opts.noHooks,
|
|
365
|
+
opts.minifier,
|
|
366
|
+
opts.minifyLevel,
|
|
367
|
+
opts.logLevel,
|
|
368
|
+
opts.envFile,
|
|
369
|
+
opts.secretsFile,
|
|
370
|
+
opts.noEnvFile,
|
|
245
371
|
]);
|
|
246
|
-
if (error) {
|
|
372
|
+
if (error && !conn) {
|
|
247
373
|
return _jsxs(Text, { color: "red", children: ["Error: ", error.message] });
|
|
248
374
|
}
|
|
249
|
-
if (!
|
|
250
|
-
return (_jsxs(Text, { children: [_jsx(Spinner, { spinner: spinners.dots }), "
|
|
375
|
+
if (!conn) {
|
|
376
|
+
return (_jsxs(Text, { children: [_jsx(Spinner, { spinner: spinners.dots }), " Starting simulator\u2026"] }));
|
|
251
377
|
}
|
|
252
|
-
return _jsx(
|
|
378
|
+
return (_jsx(InkReplConsole, { session: conn.session, devicePath: "simulator", logLevel: opts.logLevel, repl: conn.repl, watch: opts.watch }));
|
|
253
379
|
}
|
|
254
380
|
//# sourceMappingURL=dev.js.map
|