@faable/faable 1.5.27 → 1.5.28
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/commands/deploy/node-pipeline/analyze_package.js +10 -9
- package/dist/commands/deploy/node-pipeline/build_docker.js +5 -1
- package/dist/commands/deploy/node-pipeline/frameworks.js +108 -0
- package/dist/commands/deploy/node-pipeline/index.js +10 -1
- package/dist/commands/deploy/node-pipeline/inject_serve.js +20 -0
- package/dist/lib/Configuration.js +4 -0
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'fs-extra';
|
|
2
2
|
import path__default from 'path';
|
|
3
3
|
import { log } from '../../../log.js';
|
|
4
|
-
import
|
|
4
|
+
import { detect_framework } from './frameworks.js';
|
|
5
5
|
|
|
6
6
|
const analyze_package = async (params) => {
|
|
7
7
|
const workdir = params.workdir;
|
|
@@ -11,23 +11,24 @@ const analyze_package = async (params) => {
|
|
|
11
11
|
// Check if build is required to run
|
|
12
12
|
const build_script = process.env.FAABLE_NPM_BUILD_SCRIPT
|
|
13
13
|
? process.env.FAABLE_NPM_BUILD_SCRIPT
|
|
14
|
-
: pkg?.scripts["build"]
|
|
14
|
+
: pkg?.scripts?.["build"]
|
|
15
15
|
? "build"
|
|
16
16
|
: null;
|
|
17
17
|
if (!build_script) {
|
|
18
18
|
log.info(`No build script on package.json`);
|
|
19
19
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
20
|
+
const has_start = Boolean(pkg?.scripts?.["start"]);
|
|
21
|
+
const { type, start_command, inject_serve } = detect_framework({
|
|
22
|
+
pkg,
|
|
23
|
+
workdir,
|
|
24
|
+
has_start,
|
|
25
|
+
});
|
|
27
26
|
log.info(`⚡️ Detected deployment type=${type}`);
|
|
28
27
|
return {
|
|
29
28
|
build_script,
|
|
30
29
|
type,
|
|
30
|
+
start_command,
|
|
31
|
+
inject_serve,
|
|
31
32
|
};
|
|
32
33
|
};
|
|
33
34
|
|
|
@@ -27,7 +27,11 @@ const entrypoint_template = Handlebars.compile(entrypoint);
|
|
|
27
27
|
const build_docker = async (props) => {
|
|
28
28
|
const { app, workdir, template_context } = props;
|
|
29
29
|
const entrypoint_custom = entrypoint_template(template_context);
|
|
30
|
-
|
|
30
|
+
// Precedence: explicit faable.json startCommand > framework-detected command
|
|
31
|
+
// (e.g. serving a static SPA) > default `npm run start`.
|
|
32
|
+
const start_command = Configuration.instance().configuredStartCommand ??
|
|
33
|
+
props.start_command ??
|
|
34
|
+
"npm run start";
|
|
31
35
|
log.info(`⚙️ Start command: ${start_command}`);
|
|
32
36
|
// NOTE: use slim to build projects
|
|
33
37
|
const linux_distro = "slim";
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path__default from 'path';
|
|
3
|
+
import * as R from 'ramda';
|
|
4
|
+
import { log } from '../../../log.js';
|
|
5
|
+
|
|
6
|
+
const has_dep = (pkg, name) => Boolean(R.view(R.lensPath(["dependencies", name]), pkg) ||
|
|
7
|
+
R.view(R.lensPath(["devDependencies", name]), pkg));
|
|
8
|
+
/**
|
|
9
|
+
* Read Angular's build output path from angular.json. Defaults to `dist` when
|
|
10
|
+
* it can't be resolved. Angular ≥17 (application builder) emits into
|
|
11
|
+
* `<outputPath>/browser`, so we append it when the project uses that builder.
|
|
12
|
+
*/
|
|
13
|
+
const resolve_angular_output = (workdir) => {
|
|
14
|
+
const fallback = "dist";
|
|
15
|
+
try {
|
|
16
|
+
const angular_json = fs.readJSONSync(path__default.join(workdir, "angular.json"));
|
|
17
|
+
const projects = angular_json?.projects ?? {};
|
|
18
|
+
const project_name = angular_json?.defaultProject ?? Object.keys(projects)[0];
|
|
19
|
+
const build = projects?.[project_name]?.architect?.build;
|
|
20
|
+
const output = build?.options?.outputPath;
|
|
21
|
+
if (!output)
|
|
22
|
+
return fallback;
|
|
23
|
+
const builder = build?.builder ?? "";
|
|
24
|
+
const is_application_builder = builder.includes("application") || builder.includes("browser-esbuild");
|
|
25
|
+
return is_application_builder ? path__default.join(output, "browser") : output;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return fallback;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Framework registry, evaluated in order. Order matters: Astro/SvelteKit/CRA
|
|
33
|
+
* pull Vite in transitively, so Vite must be the last static fallback.
|
|
34
|
+
*/
|
|
35
|
+
const FRAMEWORKS = [
|
|
36
|
+
// Next.js: handled by its own runtime_strategy/PVC, never static-served here.
|
|
37
|
+
{ type: "next", deps: ["next"] },
|
|
38
|
+
{
|
|
39
|
+
type: "astro",
|
|
40
|
+
deps: ["astro"],
|
|
41
|
+
outputDir: "dist",
|
|
42
|
+
serveCommand: (dir) => `npx astro preview --host 0.0.0.0 --port $PORT`,
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
type: "gatsby",
|
|
46
|
+
deps: ["gatsby"],
|
|
47
|
+
outputDir: "public",
|
|
48
|
+
serveCommand: (dir) => `npx gatsby serve --host 0.0.0.0 --port $PORT`,
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
type: "cra",
|
|
52
|
+
deps: ["react-scripts"],
|
|
53
|
+
outputDir: "build",
|
|
54
|
+
injectServe: true,
|
|
55
|
+
serveCommand: (dir) => `npx serve -s ${dir} -l $PORT`,
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
type: "vue",
|
|
59
|
+
deps: ["@vue/cli-service"],
|
|
60
|
+
outputDir: "dist",
|
|
61
|
+
injectServe: true,
|
|
62
|
+
serveCommand: (dir) => `npx serve -s ${dir} -l $PORT`,
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
type: "angular",
|
|
66
|
+
deps: ["@angular/cli", "@angular-devkit/build-angular"],
|
|
67
|
+
injectServe: true,
|
|
68
|
+
resolveOutput: resolve_angular_output,
|
|
69
|
+
serveCommand: (dir) => `npx serve -s ${dir} -l $PORT`,
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
type: "vite",
|
|
73
|
+
deps: ["vite"],
|
|
74
|
+
outputDir: "dist",
|
|
75
|
+
serveCommand: (dir) => `npx vite preview --host 0.0.0.0 --port $PORT`,
|
|
76
|
+
},
|
|
77
|
+
];
|
|
78
|
+
/**
|
|
79
|
+
* Detect the framework from package.json and compute how to serve it.
|
|
80
|
+
*
|
|
81
|
+
* When the project defines its own `start` script we never override it (the app
|
|
82
|
+
* ships a real server — custom SSR, Nuxt, Remix, SvelteKit node-adapter, etc.),
|
|
83
|
+
* so `start_command`/`inject_serve` stay neutral.
|
|
84
|
+
*/
|
|
85
|
+
const detect_framework = (params) => {
|
|
86
|
+
const { pkg, workdir, has_start } = params;
|
|
87
|
+
const framework = FRAMEWORKS.find((fw) => fw.deps.some((dep) => has_dep(pkg, dep)));
|
|
88
|
+
if (!framework) {
|
|
89
|
+
return { type: "node", start_command: null, inject_serve: false };
|
|
90
|
+
}
|
|
91
|
+
// Static frameworks only override the start command when the project doesn't
|
|
92
|
+
// ship its own server.
|
|
93
|
+
if (framework.serveCommand && !has_start) {
|
|
94
|
+
const output_dir = framework.resolveOutput
|
|
95
|
+
? framework.resolveOutput(workdir)
|
|
96
|
+
: framework.outputDir ?? "dist";
|
|
97
|
+
const start_command = framework.serveCommand(output_dir);
|
|
98
|
+
log.info(`No start script on package.json, serving ${framework.type} output (${output_dir}) with [${start_command}]`);
|
|
99
|
+
return {
|
|
100
|
+
type: framework.type,
|
|
101
|
+
start_command,
|
|
102
|
+
inject_serve: Boolean(framework.injectServe),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
return { type: framework.type, start_command: null, inject_serve: false };
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export { FRAMEWORKS, detect_framework, resolve_angular_output };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { build_docker } from './build_docker.js';
|
|
2
2
|
import { analyze_package } from './analyze_package.js';
|
|
3
3
|
import { build_project } from './build_project.js';
|
|
4
|
+
import { inject_serve } from './inject_serve.js';
|
|
4
5
|
import * as R from 'ramda';
|
|
5
6
|
import { log } from '../../../log.js';
|
|
6
7
|
|
|
@@ -11,16 +12,24 @@ const build_node = async (app, options) => {
|
|
|
11
12
|
throw new Error("Runtime version not specified for node");
|
|
12
13
|
}
|
|
13
14
|
// Analyze package.json to check if build is needed
|
|
14
|
-
const { build_script, type } = await analyze_package({
|
|
15
|
+
const { build_script, type, start_command, inject_serve: needs_serve } = await analyze_package({
|
|
16
|
+
workdir,
|
|
17
|
+
});
|
|
15
18
|
// Environment variables
|
|
16
19
|
const env = R.fromPairs(env_vars.map((e) => [e.name, e.value]));
|
|
17
20
|
log.info(`Building with env variables ${Object.keys(env).join(",")}`);
|
|
18
21
|
// Do build
|
|
19
22
|
await build_project({ build_script, env });
|
|
23
|
+
// Frameworks without a bundled static server (CRA/Vue/Angular) need `serve`
|
|
24
|
+
// installed into node_modules before packaging, so it ships in the image.
|
|
25
|
+
if (needs_serve) {
|
|
26
|
+
await inject_serve(workdir);
|
|
27
|
+
}
|
|
20
28
|
// Bundle project inside a docker image
|
|
21
29
|
await build_docker({
|
|
22
30
|
app,
|
|
23
31
|
workdir,
|
|
32
|
+
start_command,
|
|
24
33
|
template_context: {
|
|
25
34
|
from: `node:${runtime.version}`,
|
|
26
35
|
},
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { log } from '../../../log.js';
|
|
2
|
+
import { cmd } from '../../../lib/cmd.js';
|
|
3
|
+
|
|
4
|
+
// Pinned for reproducible builds. `serve` is the standalone static server used
|
|
5
|
+
// for frameworks without a bundled preview tool (CRA, Vue, Angular).
|
|
6
|
+
const SERVE_VERSION = "14";
|
|
7
|
+
/**
|
|
8
|
+
* Install `serve` into the project's node_modules so it ships inside the image
|
|
9
|
+
* via `COPY . .` (the Dockerfile does no `npm install`). This lets `npx serve`
|
|
10
|
+
* resolve the local copy at container start — no runtime download needed.
|
|
11
|
+
*
|
|
12
|
+
* `--no-save` keeps the user's package.json/lockfile untouched.
|
|
13
|
+
*/
|
|
14
|
+
const inject_serve = async (workdir) => {
|
|
15
|
+
log.info(`📥 Injecting static server (serve@${SERVE_VERSION}) into image`);
|
|
16
|
+
const timeout = 5 * 60 * 1000; // 5 minute timeout
|
|
17
|
+
await cmd(`npm install serve@${SERVE_VERSION} --no-save --no-audit --no-fund`, { cwd: workdir, timeout, enableOutput: true });
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export { inject_serve };
|
|
@@ -38,6 +38,10 @@ class Configuration {
|
|
|
38
38
|
get startCommand() {
|
|
39
39
|
return this.config.startCommand || "npm run start";
|
|
40
40
|
}
|
|
41
|
+
/** Start command explicitly set in faable.json, or undefined when relying on the default. */
|
|
42
|
+
get configuredStartCommand() {
|
|
43
|
+
return this.config.startCommand;
|
|
44
|
+
}
|
|
41
45
|
get buildCommand() {
|
|
42
46
|
return this.config.buildCommand;
|
|
43
47
|
}
|