ecopages 0.2.0-alpha.42 → 0.2.0-alpha.44
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/bin/cli.js +5 -5
- package/bin/launch-plan.js +21 -22
- package/bin/node-entry-bridge.js +156 -0
- package/package.json +7 -4
package/bin/cli.js
CHANGED
|
@@ -169,7 +169,7 @@ function runLaunchPlan(launchPlan) {
|
|
|
169
169
|
});
|
|
170
170
|
child.on("error", (error) => {
|
|
171
171
|
if (error && error.code === "ENOENT") {
|
|
172
|
-
const hint = launchPlan.runtime === "bun" ? "Install Bun from https://bun.sh to continue." : "Reinstall ecopages and its dependencies so the
|
|
172
|
+
const hint = launchPlan.runtime === "bun" ? "Install Bun from https://bun.sh to continue." : "Reinstall ecopages and its dependencies so the Node entry bridge dependencies are available.";
|
|
173
173
|
logger.error(`Command not found: ${launchPlan.command}. ${hint}`);
|
|
174
174
|
process.exit(1);
|
|
175
175
|
}
|
|
@@ -182,6 +182,10 @@ function runLaunchPlan(launchPlan) {
|
|
|
182
182
|
}
|
|
183
183
|
async function runEntryCommand(args, options = {}, entryFile = "app.ts") {
|
|
184
184
|
let launchPlan;
|
|
185
|
+
if (!existsSync(entryFile)) {
|
|
186
|
+
logger.error(`Error: Entry file "${entryFile}" not found in the current directory.`);
|
|
187
|
+
process.exit(1);
|
|
188
|
+
}
|
|
185
189
|
try {
|
|
186
190
|
launchPlan = await createLaunchPlan(args, options, entryFile);
|
|
187
191
|
} catch (error) {
|
|
@@ -189,10 +193,6 @@ async function runEntryCommand(args, options = {}, entryFile = "app.ts") {
|
|
|
189
193
|
logger.error(message);
|
|
190
194
|
process.exit(1);
|
|
191
195
|
}
|
|
192
|
-
if (!existsSync(entryFile)) {
|
|
193
|
-
logger.error(`Error: Entry file "${entryFile}" not found in the current directory.`);
|
|
194
|
-
process.exit(1);
|
|
195
|
-
}
|
|
196
196
|
runLaunchPlan(launchPlan);
|
|
197
197
|
}
|
|
198
198
|
async function runInitCommand(rawArgs) {
|
package/bin/launch-plan.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
function
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { parseEnv } from "node:util";
|
|
3
|
+
import { buildNodeEntryBridge } from "./node-entry-bridge.js";
|
|
4
|
+
function getEnvFilePaths(nodeEnv) {
|
|
5
5
|
const envFiles = [".env", ".env.local"];
|
|
6
6
|
if (nodeEnv) {
|
|
7
7
|
envFiles.push(`.env.${nodeEnv}`, `.env.${nodeEnv}.local`);
|
|
8
8
|
}
|
|
9
|
-
return envFiles.filter((envFile) => existsSync(envFile))
|
|
9
|
+
return envFiles.filter((envFile) => existsSync(envFile));
|
|
10
10
|
}
|
|
11
11
|
function buildEnvOverrides(options) {
|
|
12
12
|
const env = {};
|
|
@@ -17,6 +17,16 @@ function buildEnvOverrides(options) {
|
|
|
17
17
|
if (options.nodeEnv) env.NODE_ENV = options.nodeEnv;
|
|
18
18
|
return env;
|
|
19
19
|
}
|
|
20
|
+
function buildLaunchEnv(options) {
|
|
21
|
+
const envOverrides = buildEnvOverrides(options);
|
|
22
|
+
const envFileValues = getEnvFilePaths(options.nodeEnv).reduce((env, envFile) => {
|
|
23
|
+
return { ...env, ...parseEnv(readFileSync(envFile, "utf8")) };
|
|
24
|
+
}, {});
|
|
25
|
+
return {
|
|
26
|
+
envOverrides,
|
|
27
|
+
env: { ...envFileValues, ...process.env, ...envOverrides }
|
|
28
|
+
};
|
|
29
|
+
}
|
|
20
30
|
function detectRuntime(options = {}) {
|
|
21
31
|
if (options.runtime === "bun" || options.runtime === "node") {
|
|
22
32
|
return options.runtime;
|
|
@@ -36,7 +46,7 @@ function buildBunArgs(args, options, entryFile, hasConfig) {
|
|
|
36
46
|
if (options.hot) bunArgs.push("--hot");
|
|
37
47
|
bunArgs.push("run");
|
|
38
48
|
if (hasConfig) {
|
|
39
|
-
bunArgs.push("--preload",
|
|
49
|
+
bunArgs.push("--preload", `./eco.config.${"ts"}`);
|
|
40
50
|
}
|
|
41
51
|
bunArgs.push(entryFile, ...args);
|
|
42
52
|
if (options.reactFastRefresh) {
|
|
@@ -44,26 +54,15 @@ function buildBunArgs(args, options, entryFile, hasConfig) {
|
|
|
44
54
|
}
|
|
45
55
|
return bunArgs;
|
|
46
56
|
}
|
|
47
|
-
function resolveTsxCliPath() {
|
|
48
|
-
try {
|
|
49
|
-
return require2.resolve("tsx/cli");
|
|
50
|
-
} catch {
|
|
51
|
-
throw new Error(
|
|
52
|
-
"Unable to resolve the packaged tsx runtime required for Node.js launches. Reinstall ecopages and its dependencies, or use the Bun runtime instead."
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
57
|
async function createLaunchPlan(args, options = {}, entryFile = "app.ts") {
|
|
57
|
-
const envOverrides =
|
|
58
|
+
const { envOverrides, env } = buildLaunchEnv(options);
|
|
58
59
|
const runtime = detectRuntime(options);
|
|
59
|
-
const env = { ...process.env, ...envOverrides };
|
|
60
60
|
if (runtime === "node") {
|
|
61
|
-
const
|
|
62
|
-
const nodeArgs = [entryFile, ...args];
|
|
61
|
+
const nodeEntryBridge = await buildNodeEntryBridge(entryFile);
|
|
63
62
|
return {
|
|
64
63
|
runtime,
|
|
65
64
|
command: process.execPath,
|
|
66
|
-
commandArgs: [
|
|
65
|
+
commandArgs: [nodeEntryBridge, ...args],
|
|
67
66
|
envOverrides,
|
|
68
67
|
env
|
|
69
68
|
};
|
|
@@ -79,7 +78,7 @@ async function createLaunchPlan(args, options = {}, entryFile = "app.ts") {
|
|
|
79
78
|
export {
|
|
80
79
|
buildBunArgs,
|
|
81
80
|
buildEnvOverrides,
|
|
81
|
+
buildLaunchEnv,
|
|
82
82
|
createLaunchPlan,
|
|
83
|
-
detectRuntime
|
|
84
|
-
resolveTsxCliPath
|
|
83
|
+
detectRuntime
|
|
85
84
|
};
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { lstatSync, mkdirSync, readFileSync, realpathSync, rmSync, symlinkSync, unlinkSync } from "node:fs";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
5
|
+
import { build } from "esbuild";
|
|
6
|
+
function getLoader(filePath) {
|
|
7
|
+
const extension = path.extname(filePath).toLowerCase();
|
|
8
|
+
if (extension === ".tsx") return "tsx";
|
|
9
|
+
if (extension === ".jsx") return "jsx";
|
|
10
|
+
if (extension === ".json") return "json";
|
|
11
|
+
return extension === ".ts" || extension === ".mts" || extension === ".cts" ? "ts" : "js";
|
|
12
|
+
}
|
|
13
|
+
function shouldRewriteSource(filePath, rootDir) {
|
|
14
|
+
const normalizedPath = path.resolve(filePath);
|
|
15
|
+
const normalizedRootDir = path.resolve(rootDir);
|
|
16
|
+
return normalizedPath === normalizedRootDir || normalizedPath.startsWith(`${normalizedRootDir}${path.sep}`);
|
|
17
|
+
}
|
|
18
|
+
function rewriteImportMeta(contents, filePath, entryFile) {
|
|
19
|
+
const normalizedPath = path.resolve(filePath);
|
|
20
|
+
const normalizedEntryFile = path.resolve(entryFile);
|
|
21
|
+
return contents.replaceAll("import.meta.env", "process.env").replaceAll("import.meta.main", normalizedPath === normalizedEntryFile ? "true" : "false").replaceAll("import.meta.url", JSON.stringify(pathToFileURL(filePath).href)).replaceAll("import.meta.dirname", JSON.stringify(path.dirname(filePath))).replaceAll("import.meta.filename", JSON.stringify(filePath)).replaceAll("import.meta.dir", JSON.stringify(path.dirname(filePath))).replaceAll("import.meta.path", JSON.stringify(filePath));
|
|
22
|
+
}
|
|
23
|
+
function getPackageNameFromSpecifier(specifier) {
|
|
24
|
+
if (specifier.startsWith("@")) {
|
|
25
|
+
const [scope, name] = specifier.split("/");
|
|
26
|
+
return `${scope}/${name}`;
|
|
27
|
+
}
|
|
28
|
+
return specifier.split("/")[0] ?? specifier;
|
|
29
|
+
}
|
|
30
|
+
function findPackageRoot(resolvedPath) {
|
|
31
|
+
let currentPath = path.dirname(resolvedPath);
|
|
32
|
+
while (true) {
|
|
33
|
+
const packageJsonPath = path.join(currentPath, "package.json");
|
|
34
|
+
try {
|
|
35
|
+
lstatSync(packageJsonPath);
|
|
36
|
+
return currentPath;
|
|
37
|
+
} catch {
|
|
38
|
+
}
|
|
39
|
+
const parentPath = path.dirname(currentPath);
|
|
40
|
+
if (parentPath === currentPath) {
|
|
41
|
+
throw new Error(`Could not find package root for resolved dependency path: ${resolvedPath}`);
|
|
42
|
+
}
|
|
43
|
+
currentPath = parentPath;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function linkPointsToPackage(linkPath, packageRoot) {
|
|
47
|
+
try {
|
|
48
|
+
return realpathSync(linkPath) === realpathSync(packageRoot);
|
|
49
|
+
} catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function removeRuntimePackageLink(linkPath) {
|
|
54
|
+
try {
|
|
55
|
+
const stats = lstatSync(linkPath);
|
|
56
|
+
if (stats.isSymbolicLink()) {
|
|
57
|
+
unlinkSync(linkPath);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
} catch {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
rmSync(linkPath, { recursive: true, force: true });
|
|
64
|
+
}
|
|
65
|
+
function ensureRuntimePackageLink(nodeModulesDir, specifier, resolvedPath) {
|
|
66
|
+
const packageName = getPackageNameFromSpecifier(specifier);
|
|
67
|
+
const packageRoot = findPackageRoot(resolvedPath);
|
|
68
|
+
const linkPath = path.join(nodeModulesDir, packageName);
|
|
69
|
+
mkdirSync(path.dirname(linkPath), { recursive: true });
|
|
70
|
+
try {
|
|
71
|
+
lstatSync(linkPath);
|
|
72
|
+
if (linkPointsToPackage(linkPath, packageRoot)) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
removeRuntimePackageLink(linkPath);
|
|
76
|
+
} catch {
|
|
77
|
+
}
|
|
78
|
+
symlinkSync(packageRoot, linkPath, "dir");
|
|
79
|
+
}
|
|
80
|
+
function createNodeEntryBridgePlugin({ rootDir, entryFile, runtimeNodeModulesDir }) {
|
|
81
|
+
return {
|
|
82
|
+
name: "ecopages-node-entry-bridge",
|
|
83
|
+
setup(buildContext) {
|
|
84
|
+
buildContext.onResolve({ filter: /^[@A-Za-z0-9][^:]*$/ }, (args) => {
|
|
85
|
+
try {
|
|
86
|
+
const resolvedPath = createRequire(path.join(args.resolveDir || rootDir, "package.json")).resolve(
|
|
87
|
+
args.path
|
|
88
|
+
);
|
|
89
|
+
if (!args.path.startsWith("@ecopages/")) {
|
|
90
|
+
ensureRuntimePackageLink(runtimeNodeModulesDir, args.path, resolvedPath);
|
|
91
|
+
}
|
|
92
|
+
if (resolvedPath.endsWith(".node")) {
|
|
93
|
+
return {
|
|
94
|
+
path: args.path,
|
|
95
|
+
external: true
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
if (!args.path.startsWith("@ecopages/")) {
|
|
99
|
+
return {
|
|
100
|
+
path: args.path,
|
|
101
|
+
external: true
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
} catch {
|
|
105
|
+
return void 0;
|
|
106
|
+
}
|
|
107
|
+
return void 0;
|
|
108
|
+
});
|
|
109
|
+
buildContext.onResolve({ filter: /\.node$/ }, (args) => ({
|
|
110
|
+
path: path.isAbsolute(args.path) ? args.path : path.resolve(args.resolveDir, args.path),
|
|
111
|
+
external: true
|
|
112
|
+
}));
|
|
113
|
+
buildContext.onLoad({ filter: /\.[cm]?[jt]sx?$/ }, (args) => {
|
|
114
|
+
if (!shouldRewriteSource(args.path, rootDir)) {
|
|
115
|
+
return void 0;
|
|
116
|
+
}
|
|
117
|
+
const contents = rewriteImportMeta(readFileSync(args.path, "utf8"), args.path, entryFile);
|
|
118
|
+
return {
|
|
119
|
+
contents,
|
|
120
|
+
loader: getLoader(args.path),
|
|
121
|
+
resolveDir: path.dirname(args.path)
|
|
122
|
+
};
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
async function buildNodeEntryBridge(entryFile, options = {}) {
|
|
128
|
+
const rootDir = path.resolve(options.rootDir ?? process.cwd());
|
|
129
|
+
const absoluteEntryFile = path.resolve(rootDir, entryFile);
|
|
130
|
+
const outdir = path.resolve(rootDir, ".eco", "node-entry");
|
|
131
|
+
const outfile = path.join(outdir, "app-entry.mjs");
|
|
132
|
+
rmSync(outdir, { recursive: true, force: true });
|
|
133
|
+
mkdirSync(outdir, { recursive: true });
|
|
134
|
+
await build({
|
|
135
|
+
absWorkingDir: rootDir,
|
|
136
|
+
entryPoints: [absoluteEntryFile],
|
|
137
|
+
outfile,
|
|
138
|
+
bundle: true,
|
|
139
|
+
format: "esm",
|
|
140
|
+
platform: "node",
|
|
141
|
+
target: "es2022",
|
|
142
|
+
sourcemap: "linked",
|
|
143
|
+
logLevel: "silent",
|
|
144
|
+
plugins: [
|
|
145
|
+
createNodeEntryBridgePlugin({
|
|
146
|
+
rootDir,
|
|
147
|
+
entryFile: absoluteEntryFile,
|
|
148
|
+
runtimeNodeModulesDir: path.join(outdir, "node_modules")
|
|
149
|
+
})
|
|
150
|
+
]
|
|
151
|
+
});
|
|
152
|
+
return outfile;
|
|
153
|
+
}
|
|
154
|
+
export {
|
|
155
|
+
buildNodeEntryBridge
|
|
156
|
+
};
|
package/package.json
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ecopages",
|
|
3
|
-
"version": "0.2.0-alpha.
|
|
3
|
+
"version": "0.2.0-alpha.44",
|
|
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": {
|
|
@@ -29,10 +32,10 @@
|
|
|
29
32
|
"ecopages": "bin/cli.js"
|
|
30
33
|
},
|
|
31
34
|
"dependencies": {
|
|
32
|
-
"@ecopages/core": "0.2.0-alpha.
|
|
35
|
+
"@ecopages/core": "0.2.0-alpha.44",
|
|
33
36
|
"@ecopages/logger": "^0.2.3",
|
|
34
|
-
"
|
|
35
|
-
"
|
|
37
|
+
"esbuild": "^0.28.0",
|
|
38
|
+
"giget": "^2.0.0"
|
|
36
39
|
},
|
|
37
40
|
"peerDependencies": {
|
|
38
41
|
"bun-types": "*",
|