ecopages 0.2.0-alpha.9 → 0.2.0-beta.0
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/README.md +22 -31
- package/bin/cli.js +308 -164
- package/bin/launch-plan.js +78 -136
- package/bin/node-require-preload.js +3 -0
- package/package.json +8 -11
- package/bin/launch-plan.test.ts +0 -316
- package/bin/node-thin-host.js +0 -138
- package/bin/node-thin-host.test.ts +0 -167
package/bin/launch-plan.js
CHANGED
|
@@ -1,142 +1,84 @@
|
|
|
1
|
-
import { existsSync,
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
if (options.hostname) env.ECOPAGES_HOSTNAME = options.hostname;
|
|
12
|
-
if (options.baseUrl) env.ECOPAGES_BASE_URL = options.baseUrl;
|
|
13
|
-
if (options.debug) env.ECOPAGES_LOGGER_DEBUG = 'true';
|
|
14
|
-
if (options.nodeEnv) env.NODE_ENV = options.nodeEnv;
|
|
15
|
-
return env;
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { parseEnv } from "node:util";
|
|
3
|
+
const nodeRequirePreload = import.meta.resolve("./node-require-preload.js");
|
|
4
|
+
const tsxLoader = import.meta.resolve("tsx/esm");
|
|
5
|
+
function getEnvFilePaths(nodeEnv) {
|
|
6
|
+
const envFiles = [".env", ".env.local"];
|
|
7
|
+
if (nodeEnv) {
|
|
8
|
+
envFiles.push(`.env.${nodeEnv}`, `.env.${nodeEnv}.local`);
|
|
9
|
+
}
|
|
10
|
+
return envFiles.filter((envFile) => existsSync(envFile));
|
|
16
11
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
if (userAgent.startsWith('bun/')) {
|
|
26
|
-
return 'bun';
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
if (typeof Bun !== 'undefined') {
|
|
30
|
-
return 'bun';
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
return 'node';
|
|
12
|
+
function buildEnvOverrides(options) {
|
|
13
|
+
const env = {};
|
|
14
|
+
if (options.port) env.ECOPAGES_PORT = String(options.port);
|
|
15
|
+
if (options.hostname) env.ECOPAGES_HOSTNAME = options.hostname;
|
|
16
|
+
if (options.baseUrl) env.ECOPAGES_BASE_URL = options.baseUrl;
|
|
17
|
+
if (options.debug) env.ECOPAGES_LOGGER_DEBUG = "true";
|
|
18
|
+
if (options.nodeEnv) env.NODE_ENV = options.nodeEnv;
|
|
19
|
+
return env;
|
|
34
20
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
if (hasConfig) {
|
|
45
|
-
bunArgs.push('--preload', 'eco.config.ts');
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
bunArgs.push(entryFile, ...args);
|
|
49
|
-
|
|
50
|
-
if (options.reactFastRefresh) {
|
|
51
|
-
bunArgs.push('--react-fast-refresh');
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return bunArgs;
|
|
21
|
+
function buildLaunchEnv(options) {
|
|
22
|
+
const envOverrides = buildEnvOverrides(options);
|
|
23
|
+
const envFileValues = getEnvFilePaths(options.nodeEnv).reduce((env, envFile) => {
|
|
24
|
+
return { ...env, ...parseEnv(readFileSync(envFile, "utf8")) };
|
|
25
|
+
}, {});
|
|
26
|
+
return {
|
|
27
|
+
envOverrides,
|
|
28
|
+
env: { ...envFileValues, ...process.env, ...envOverrides }
|
|
29
|
+
};
|
|
55
30
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
return nodeArgs;
|
|
31
|
+
function detectRuntime(options = {}) {
|
|
32
|
+
if (options.runtime === "bun" || options.runtime === "node") {
|
|
33
|
+
return options.runtime;
|
|
34
|
+
}
|
|
35
|
+
const userAgent = process.env.npm_config_user_agent || "";
|
|
36
|
+
if (userAgent.startsWith("bun/")) {
|
|
37
|
+
return "bun";
|
|
38
|
+
}
|
|
39
|
+
if (typeof Bun !== "undefined") {
|
|
40
|
+
return "bun";
|
|
41
|
+
}
|
|
42
|
+
return "node";
|
|
69
43
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
44
|
+
function buildBunArgs(args, options, entryFile, hasConfig) {
|
|
45
|
+
const bunArgs = [];
|
|
46
|
+
if (options.watch) bunArgs.push("--watch");
|
|
47
|
+
if (options.hot) bunArgs.push("--hot");
|
|
48
|
+
bunArgs.push("run");
|
|
49
|
+
if (hasConfig) {
|
|
50
|
+
bunArgs.push("--preload", `./eco.config.${"ts"}`);
|
|
51
|
+
}
|
|
52
|
+
bunArgs.push(entryFile, ...args);
|
|
53
|
+
if (options.reactFastRefresh) {
|
|
54
|
+
bunArgs.push("--react-fast-refresh");
|
|
55
|
+
}
|
|
56
|
+
return bunArgs;
|
|
73
57
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
sourceRootDir: path.join(projectDir, 'src'),
|
|
94
|
-
distDir: path.join(projectDir, 'dist'),
|
|
95
|
-
workDir: path.join(projectDir, DEFAULT_INTERNAL_WORK_DIR),
|
|
96
|
-
modulePaths: {
|
|
97
|
-
config: configPath,
|
|
98
|
-
entry: path.resolve(projectDir, entryFile),
|
|
99
|
-
},
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
mkdirSync(path.dirname(manifestFilePath), { recursive: true });
|
|
103
|
-
writeFileSync(manifestFilePath, `${JSON.stringify(manifest, null, 2)}\n`, 'utf8');
|
|
104
|
-
|
|
105
|
-
return manifestFilePath;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
export async function createLaunchPlan(args, options = {}, entryFile = 'app.ts') {
|
|
109
|
-
const hasConfig = existsSync('eco.config.ts');
|
|
110
|
-
const envOverrides = buildEnvOverrides(options);
|
|
111
|
-
const runtime = detectRuntime(options);
|
|
112
|
-
const env = { ...process.env, ...envOverrides };
|
|
113
|
-
|
|
114
|
-
if (runtime === 'node' || runtime === 'node-experimental') {
|
|
115
|
-
const manifestFilePath = await createNodeRuntimeManifestFile(entryFile, { env });
|
|
116
|
-
|
|
117
|
-
return {
|
|
118
|
-
runtime,
|
|
119
|
-
executionStrategy: 'node-thin-host',
|
|
120
|
-
command: 'node',
|
|
121
|
-
commandArgs: buildNodeArgs(args, options, entryFile),
|
|
122
|
-
envOverrides,
|
|
123
|
-
env: {
|
|
124
|
-
...env,
|
|
125
|
-
ECOPAGES_NODE_RUNTIME_MANIFEST_PATH: manifestFilePath,
|
|
126
|
-
},
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
return {
|
|
131
|
-
runtime,
|
|
132
|
-
executionStrategy: 'direct-runtime',
|
|
133
|
-
command: 'bun',
|
|
134
|
-
commandArgs: buildBunArgs(args, options, entryFile, hasConfig),
|
|
135
|
-
envOverrides,
|
|
136
|
-
env,
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
export function launchPlanRequiresExistingEntryFile(launchPlan) {
|
|
141
|
-
return launchPlan.executionStrategy !== 'config-only-bootstrap';
|
|
58
|
+
function createLaunchPlan(args, options = {}, entryFile = "app.ts") {
|
|
59
|
+
const { envOverrides, env } = buildLaunchEnv(options);
|
|
60
|
+
const runtime = detectRuntime(options);
|
|
61
|
+
if (runtime === "node") {
|
|
62
|
+
return {
|
|
63
|
+
runtime,
|
|
64
|
+
command: process.execPath,
|
|
65
|
+
commandArgs: ["--import", nodeRequirePreload, "--import", tsxLoader, entryFile, ...args],
|
|
66
|
+
envOverrides,
|
|
67
|
+
env
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
runtime,
|
|
72
|
+
command: "bun",
|
|
73
|
+
commandArgs: buildBunArgs(args, options, entryFile, existsSync("eco.config.ts")),
|
|
74
|
+
envOverrides,
|
|
75
|
+
env
|
|
76
|
+
};
|
|
142
77
|
}
|
|
78
|
+
export {
|
|
79
|
+
buildBunArgs,
|
|
80
|
+
buildEnvOverrides,
|
|
81
|
+
buildLaunchEnv,
|
|
82
|
+
createLaunchPlan,
|
|
83
|
+
detectRuntime
|
|
84
|
+
};
|
package/package.json
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ecopages",
|
|
3
|
-
"version": "0.2.0-
|
|
3
|
+
"version": "0.2.0-beta.0",
|
|
4
4
|
"description": "CLI utilities for Ecopages",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=24.0.0"
|
|
8
|
+
},
|
|
6
9
|
"license": "MIT",
|
|
7
10
|
"author": "Ecopages Team",
|
|
8
11
|
"repository": {
|
|
@@ -28,17 +31,11 @@
|
|
|
28
31
|
"bin": {
|
|
29
32
|
"ecopages": "bin/cli.js"
|
|
30
33
|
},
|
|
31
|
-
"files": [
|
|
32
|
-
"bin/",
|
|
33
|
-
"css/",
|
|
34
|
-
"README.md"
|
|
35
|
-
],
|
|
36
34
|
"dependencies": {
|
|
37
|
-
"@ecopages/core": "
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"@ecopages/logger": "^0.2.2"
|
|
35
|
+
"@ecopages/core": "0.2.0-beta.0",
|
|
36
|
+
"@ecopages/logger": "^0.2.3",
|
|
37
|
+
"giget": "^2.0.0",
|
|
38
|
+
"tsx": "^4.22.3"
|
|
42
39
|
},
|
|
43
40
|
"peerDependencies": {
|
|
44
41
|
"bun-types": "*",
|
package/bin/launch-plan.test.ts
DELETED
|
@@ -1,316 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { tmpdir } from 'node:os';
|
|
4
|
-
import { afterEach, describe, expect, it } from 'vitest';
|
|
5
|
-
import {
|
|
6
|
-
buildEnvOverrides,
|
|
7
|
-
buildNodeArgs,
|
|
8
|
-
buildBunArgs,
|
|
9
|
-
createNodeRuntimeManifestFile,
|
|
10
|
-
createLaunchPlan,
|
|
11
|
-
detectRuntime,
|
|
12
|
-
launchPlanRequiresExistingEntryFile,
|
|
13
|
-
resolveNodeRuntimeManifestPath,
|
|
14
|
-
} from './launch-plan.js';
|
|
15
|
-
|
|
16
|
-
const originalUserAgent = process.env.npm_config_user_agent;
|
|
17
|
-
|
|
18
|
-
afterEach(() => {
|
|
19
|
-
if (originalUserAgent === undefined) {
|
|
20
|
-
delete process.env.npm_config_user_agent;
|
|
21
|
-
} else {
|
|
22
|
-
process.env.npm_config_user_agent = originalUserAgent;
|
|
23
|
-
}
|
|
24
|
-
process.chdir('/Users/andeeplus/github/ecopages');
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
describe('launch-plan', () => {
|
|
28
|
-
function writeExperimentalRuntimeConfig(tempDir) {
|
|
29
|
-
fs.writeFileSync(
|
|
30
|
-
path.join(tempDir, 'eco.config.ts'),
|
|
31
|
-
[
|
|
32
|
-
'const rootDir = process.cwd();',
|
|
33
|
-
'export default {',
|
|
34
|
-
'\trootDir,',
|
|
35
|
-
'\tloaders: new Map(),',
|
|
36
|
-
'\tabsolutePaths: {',
|
|
37
|
-
'\t\tconfig: `${rootDir}/eco.config.ts`,',
|
|
38
|
-
'\t\tsrcDir: `${rootDir}/src`,',
|
|
39
|
-
'\t\tdistDir: `${rootDir}/dist`,',
|
|
40
|
-
'\t\tworkDir: `${rootDir}/.eco`,',
|
|
41
|
-
'\t},',
|
|
42
|
-
'\truntime: {},',
|
|
43
|
-
'};',
|
|
44
|
-
].join('\n'),
|
|
45
|
-
'utf8',
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function writeImportMetaRuntimeConfig(tempDir) {
|
|
50
|
-
fs.writeFileSync(
|
|
51
|
-
path.join(tempDir, 'eco.config.ts'),
|
|
52
|
-
[
|
|
53
|
-
"import path from 'node:path';",
|
|
54
|
-
'export default {',
|
|
55
|
-
'\trootDir: import.meta.dirname,',
|
|
56
|
-
'\tloaders: new Map(),',
|
|
57
|
-
'\tabsolutePaths: {',
|
|
58
|
-
"\t\tconfig: path.join(import.meta.dirname, 'eco.config.ts'),",
|
|
59
|
-
"\t\tsrcDir: path.join(import.meta.dirname, 'src'),",
|
|
60
|
-
"\t\tdistDir: path.join(import.meta.dirname, 'dist'),",
|
|
61
|
-
"\t\tworkDir: path.join(import.meta.dirname, '.eco'),",
|
|
62
|
-
'\t},',
|
|
63
|
-
'\truntime: {},',
|
|
64
|
-
'};',
|
|
65
|
-
].join('\n'),
|
|
66
|
-
'utf8',
|
|
67
|
-
);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
it('buildEnvOverrides maps CLI options onto environment variables', () => {
|
|
71
|
-
expect(
|
|
72
|
-
buildEnvOverrides({
|
|
73
|
-
port: 4173,
|
|
74
|
-
hostname: '127.0.0.1',
|
|
75
|
-
baseUrl: 'https://example.test',
|
|
76
|
-
debug: true,
|
|
77
|
-
nodeEnv: 'production',
|
|
78
|
-
}),
|
|
79
|
-
).toEqual({
|
|
80
|
-
ECOPAGES_PORT: '4173',
|
|
81
|
-
ECOPAGES_HOSTNAME: '127.0.0.1',
|
|
82
|
-
ECOPAGES_BASE_URL: 'https://example.test',
|
|
83
|
-
ECOPAGES_LOGGER_DEBUG: 'true',
|
|
84
|
-
NODE_ENV: 'production',
|
|
85
|
-
});
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it('detectRuntime defaults to node unless bun is explicit', () => {
|
|
89
|
-
process.env.npm_config_user_agent = 'pnpm/10.0.0 npm/? node/v24.0.0 darwin arm64';
|
|
90
|
-
expect(detectRuntime()).toBe('node');
|
|
91
|
-
expect(detectRuntime({ runtime: 'bun' })).toBe('bun');
|
|
92
|
-
expect(detectRuntime({ runtime: 'node' })).toBe('node');
|
|
93
|
-
expect(detectRuntime({ runtime: 'node-experimental' })).toBe('node-experimental');
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
it('buildNodeArgs preserves watch mode and fast refresh flags', () => {
|
|
97
|
-
expect(buildNodeArgs(['--dev'], { watch: true, reactFastRefresh: true }, 'app.ts')).toEqual([
|
|
98
|
-
'--watch',
|
|
99
|
-
expect.stringMatching(/node-thin-host\.js$/),
|
|
100
|
-
'app.ts',
|
|
101
|
-
'--dev',
|
|
102
|
-
'--react-fast-refresh',
|
|
103
|
-
]);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it('buildNodeArgs routes stable Node through the thin launcher', () => {
|
|
107
|
-
expect(buildNodeArgs(['--dev'], { watch: true }, 'app.ts')).toEqual([
|
|
108
|
-
'--watch',
|
|
109
|
-
expect.stringMatching(/node-thin-host\.js$/),
|
|
110
|
-
'app.ts',
|
|
111
|
-
'--dev',
|
|
112
|
-
]);
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
it('buildBunArgs preloads eco.config.ts when present', () => {
|
|
116
|
-
expect(buildBunArgs(['--dev'], { hot: true }, 'app.ts', true)).toEqual([
|
|
117
|
-
'--hot',
|
|
118
|
-
'run',
|
|
119
|
-
'--preload',
|
|
120
|
-
'eco.config.ts',
|
|
121
|
-
'app.ts',
|
|
122
|
-
'--dev',
|
|
123
|
-
]);
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
it('createLaunchPlan routes node app launches through the thin host', async () => {
|
|
127
|
-
const tempDir = fs.mkdtempSync(path.join(tmpdir(), 'eco-cli-launch-plan-'));
|
|
128
|
-
try {
|
|
129
|
-
process.env.npm_config_user_agent = 'pnpm/10.0.0 npm/? node/v24.0.0 darwin arm64';
|
|
130
|
-
process.chdir(tempDir);
|
|
131
|
-
writeExperimentalRuntimeConfig(tempDir);
|
|
132
|
-
fs.writeFileSync(
|
|
133
|
-
path.join(tempDir, 'app.ts'),
|
|
134
|
-
[
|
|
135
|
-
"import { EcopagesApp } from '@ecopages/core';",
|
|
136
|
-
"import appConfig from './eco.config';",
|
|
137
|
-
'const app = new EcopagesApp({ appConfig });',
|
|
138
|
-
'await app.start();',
|
|
139
|
-
].join('\n'),
|
|
140
|
-
'utf8',
|
|
141
|
-
);
|
|
142
|
-
const plan = await createLaunchPlan(['--dev'], { watch: true, nodeEnv: 'development' }, 'app.ts');
|
|
143
|
-
|
|
144
|
-
expect(plan).toMatchObject({
|
|
145
|
-
runtime: 'node',
|
|
146
|
-
executionStrategy: 'node-thin-host',
|
|
147
|
-
command: 'node',
|
|
148
|
-
envOverrides: { NODE_ENV: 'development' },
|
|
149
|
-
});
|
|
150
|
-
expect(plan.commandArgs).toEqual([
|
|
151
|
-
'--watch',
|
|
152
|
-
expect.stringMatching(/node-thin-host\.js$/),
|
|
153
|
-
'app.ts',
|
|
154
|
-
'--dev',
|
|
155
|
-
]);
|
|
156
|
-
} finally {
|
|
157
|
-
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
158
|
-
}
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
it('createLaunchPlan preserves direct node entrypoints on the thin-host path', async () => {
|
|
162
|
-
const tempDir = fs.mkdtempSync(path.join(tmpdir(), 'eco-cli-launch-plan-'));
|
|
163
|
-
try {
|
|
164
|
-
process.env.npm_config_user_agent = 'pnpm/10.0.0 npm/? node/v24.0.0 darwin arm64';
|
|
165
|
-
process.chdir(tempDir);
|
|
166
|
-
fs.writeFileSync(path.join(tempDir, 'server.ts'), 'await Promise.resolve();', 'utf8');
|
|
167
|
-
writeExperimentalRuntimeConfig(tempDir);
|
|
168
|
-
|
|
169
|
-
const plan = await createLaunchPlan(['--dev'], { watch: true }, 'server.ts');
|
|
170
|
-
|
|
171
|
-
expect(plan).toMatchObject({
|
|
172
|
-
runtime: 'node',
|
|
173
|
-
executionStrategy: 'node-thin-host',
|
|
174
|
-
command: 'node',
|
|
175
|
-
});
|
|
176
|
-
expect(plan.commandArgs).toEqual([
|
|
177
|
-
'--watch',
|
|
178
|
-
expect.stringMatching(/node-thin-host\.js$/),
|
|
179
|
-
'server.ts',
|
|
180
|
-
'--dev',
|
|
181
|
-
]);
|
|
182
|
-
} finally {
|
|
183
|
-
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
184
|
-
}
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
it('createLaunchPlan uses bun direct runtime and preloads eco.config.ts', async () => {
|
|
188
|
-
const tempDir = fs.mkdtempSync(path.join(tmpdir(), 'eco-cli-launch-plan-'));
|
|
189
|
-
try {
|
|
190
|
-
process.chdir(tempDir);
|
|
191
|
-
fs.writeFileSync(path.join(tempDir, 'app.ts'), 'await Promise.resolve();', 'utf8');
|
|
192
|
-
writeExperimentalRuntimeConfig(tempDir);
|
|
193
|
-
|
|
194
|
-
const plan = await createLaunchPlan(['--preview'], { runtime: 'bun' }, 'app.ts');
|
|
195
|
-
|
|
196
|
-
expect(plan).toMatchObject({
|
|
197
|
-
runtime: 'bun',
|
|
198
|
-
executionStrategy: 'direct-runtime',
|
|
199
|
-
command: 'bun',
|
|
200
|
-
});
|
|
201
|
-
expect(plan.commandArgs).toEqual(['run', '--preload', 'eco.config.ts', 'app.ts', '--preview']);
|
|
202
|
-
} finally {
|
|
203
|
-
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
204
|
-
}
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
it('createNodeRuntimeManifestFile writes the core-owned manifest under .eco/runtime', async () => {
|
|
208
|
-
const tempDir = fs.mkdtempSync(path.join(tmpdir(), 'eco-cli-launch-plan-'));
|
|
209
|
-
try {
|
|
210
|
-
process.chdir(tempDir);
|
|
211
|
-
writeExperimentalRuntimeConfig(tempDir);
|
|
212
|
-
const manifestFilePath = await createNodeRuntimeManifestFile('app.ts');
|
|
213
|
-
const manifest = JSON.parse(fs.readFileSync(manifestFilePath, 'utf8'));
|
|
214
|
-
const resolvedTempDir = fs.realpathSync(tempDir);
|
|
215
|
-
|
|
216
|
-
expect(manifestFilePath).toBe(resolveNodeRuntimeManifestPath(resolvedTempDir));
|
|
217
|
-
expect(manifest).toMatchObject({
|
|
218
|
-
runtime: 'node',
|
|
219
|
-
appRootDir: resolvedTempDir,
|
|
220
|
-
sourceRootDir: path.join(resolvedTempDir, 'src'),
|
|
221
|
-
distDir: path.join(resolvedTempDir, 'dist'),
|
|
222
|
-
workDir: path.join(resolvedTempDir, '.eco'),
|
|
223
|
-
modulePaths: {
|
|
224
|
-
config: path.join(resolvedTempDir, 'eco.config.ts'),
|
|
225
|
-
entry: path.join(resolvedTempDir, 'app.ts'),
|
|
226
|
-
},
|
|
227
|
-
});
|
|
228
|
-
} finally {
|
|
229
|
-
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
230
|
-
}
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
it('createNodeRuntimeManifestFile does not evaluate eco.config.ts while writing the manifest', async () => {
|
|
234
|
-
const tempDir = fs.mkdtempSync(path.join(tmpdir(), 'eco-cli-launch-plan-'));
|
|
235
|
-
try {
|
|
236
|
-
process.chdir(tempDir);
|
|
237
|
-
writeImportMetaRuntimeConfig(tempDir);
|
|
238
|
-
const manifestFilePath = await createNodeRuntimeManifestFile('app.ts');
|
|
239
|
-
const manifest = JSON.parse(fs.readFileSync(manifestFilePath, 'utf8'));
|
|
240
|
-
const resolvedTempDir = fs.realpathSync(tempDir);
|
|
241
|
-
|
|
242
|
-
expect(manifestFilePath).toBe(resolveNodeRuntimeManifestPath(resolvedTempDir));
|
|
243
|
-
expect(manifest).toMatchObject({
|
|
244
|
-
appRootDir: resolvedTempDir,
|
|
245
|
-
sourceRootDir: path.join(resolvedTempDir, 'src'),
|
|
246
|
-
distDir: path.join(resolvedTempDir, 'dist'),
|
|
247
|
-
workDir: path.join(resolvedTempDir, '.eco'),
|
|
248
|
-
modulePaths: {
|
|
249
|
-
config: path.join(resolvedTempDir, 'eco.config.ts'),
|
|
250
|
-
entry: path.join(resolvedTempDir, 'app.ts'),
|
|
251
|
-
},
|
|
252
|
-
});
|
|
253
|
-
} finally {
|
|
254
|
-
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
255
|
-
}
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
it('createLaunchPlan routes node-experimental through the thin host launcher', async () => {
|
|
259
|
-
const tempDir = fs.mkdtempSync(path.join(tmpdir(), 'eco-cli-launch-plan-'));
|
|
260
|
-
try {
|
|
261
|
-
process.chdir(tempDir);
|
|
262
|
-
writeExperimentalRuntimeConfig(tempDir);
|
|
263
|
-
const resolvedTempDir = fs.realpathSync(tempDir);
|
|
264
|
-
const plan = await createLaunchPlan(
|
|
265
|
-
['--dev'],
|
|
266
|
-
{ runtime: 'node-experimental', nodeEnv: 'development' },
|
|
267
|
-
'app.ts',
|
|
268
|
-
);
|
|
269
|
-
|
|
270
|
-
expect(plan).toMatchObject({
|
|
271
|
-
runtime: 'node-experimental',
|
|
272
|
-
executionStrategy: 'node-thin-host',
|
|
273
|
-
command: 'node',
|
|
274
|
-
envOverrides: { NODE_ENV: 'development' },
|
|
275
|
-
});
|
|
276
|
-
expect(plan.commandArgs).toEqual([expect.stringMatching(/node-thin-host\.js$/), 'app.ts', '--dev']);
|
|
277
|
-
expect(plan.env.ECOPAGES_NODE_RUNTIME_MANIFEST_PATH).toBe(resolveNodeRuntimeManifestPath(resolvedTempDir));
|
|
278
|
-
expect(JSON.parse(fs.readFileSync(plan.env.ECOPAGES_NODE_RUNTIME_MANIFEST_PATH, 'utf8'))).toMatchObject({
|
|
279
|
-
runtime: 'node',
|
|
280
|
-
modulePaths: {
|
|
281
|
-
config: path.join(resolvedTempDir, 'eco.config.ts'),
|
|
282
|
-
entry: path.join(resolvedTempDir, 'app.ts'),
|
|
283
|
-
},
|
|
284
|
-
});
|
|
285
|
-
} finally {
|
|
286
|
-
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
287
|
-
}
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
it('launchPlanRequiresExistingEntryFile requires a concrete entry on every runtime path', async () => {
|
|
291
|
-
const tempDir = fs.mkdtempSync(path.join(tmpdir(), 'eco-cli-launch-plan-'));
|
|
292
|
-
try {
|
|
293
|
-
process.env.npm_config_user_agent = 'pnpm/10.0.0 npm/? node/v24.0.0 darwin arm64';
|
|
294
|
-
process.chdir(tempDir);
|
|
295
|
-
fs.writeFileSync(path.join(tempDir, 'app.ts'), 'await Promise.resolve();', 'utf8');
|
|
296
|
-
writeExperimentalRuntimeConfig(tempDir);
|
|
297
|
-
|
|
298
|
-
const nodePlan = await createLaunchPlan(['--dev'], { nodeEnv: 'development' }, 'app.ts');
|
|
299
|
-
expect(nodePlan.executionStrategy).toBe('node-thin-host');
|
|
300
|
-
expect(launchPlanRequiresExistingEntryFile(nodePlan)).toBe(true);
|
|
301
|
-
|
|
302
|
-
fs.writeFileSync(path.join(tempDir, 'server.ts'), 'await Promise.resolve();', 'utf8');
|
|
303
|
-
const directNodePlan = await createLaunchPlan(['--dev'], { nodeEnv: 'development' }, 'server.ts');
|
|
304
|
-
expect(directNodePlan.executionStrategy).toBe('node-thin-host');
|
|
305
|
-
expect(launchPlanRequiresExistingEntryFile(directNodePlan)).toBe(true);
|
|
306
|
-
|
|
307
|
-
const bunPlan = await createLaunchPlan(['--dev'], { runtime: 'bun' }, 'app.ts');
|
|
308
|
-
expect(launchPlanRequiresExistingEntryFile(bunPlan)).toBe(true);
|
|
309
|
-
|
|
310
|
-
const experimentalNodePlan = await createLaunchPlan(['--dev'], { runtime: 'node-experimental' }, 'app.ts');
|
|
311
|
-
expect(launchPlanRequiresExistingEntryFile(experimentalNodePlan)).toBe(true);
|
|
312
|
-
} finally {
|
|
313
|
-
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
314
|
-
}
|
|
315
|
-
});
|
|
316
|
-
});
|