@faable/faable 1.15.1 → 1.15.2

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.
@@ -1,8 +1,8 @@
1
- import fs from 'fs-extra';
2
1
  import path__default from 'path';
3
2
  import { cmd } from '../../../../lib/cmd.js';
4
3
  import { log } from '../../../../log.js';
5
4
  import { has_any_of_files } from '../shared/has_any_of_files.js';
5
+ import { read_json_file } from '../shared/read_text_file.js';
6
6
 
7
7
  // A package.json with a next dependency beside the Dockerfile means this is a
8
8
  // Next.js app shipped with a custom image: emit type "next" so the backend
@@ -10,7 +10,7 @@ import { has_any_of_files } from '../shared/has_any_of_files.js';
10
10
  // --buildpack override, since the node buildpack claims package.json first.
11
11
  const detect_next = (workdir) => {
12
12
  try {
13
- const pkg = fs.readJSONSync(path__default.join(workdir, "package.json"));
13
+ const pkg = read_json_file(path__default.join(workdir, "package.json"));
14
14
  return Boolean(pkg?.dependencies?.next || pkg?.devDependencies?.next);
15
15
  }
16
16
  catch {
@@ -1,13 +1,13 @@
1
- import fs from 'fs-extra';
2
1
  import path__default from 'path';
3
2
  import { log } from '../../../../log.js';
3
+ import { read_json_file } from '../shared/read_text_file.js';
4
4
  import { detect_framework } from './frameworks.js';
5
5
 
6
6
  const analyze_package = async (params) => {
7
7
  const workdir = params.workdir;
8
8
  const package_file = path__default.join(path__default.resolve(workdir), "package.json");
9
9
  log.info(`Loading config from package.json`);
10
- const pkg = await fs.readJSON(package_file);
10
+ const pkg = read_json_file(package_file);
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
@@ -1,7 +1,7 @@
1
- import fs from 'fs-extra';
2
1
  import path__default from 'path';
3
2
  import * as R from 'ramda';
4
3
  import { log } from '../../../../log.js';
4
+ import { read_json_file } from '../shared/read_text_file.js';
5
5
 
6
6
  const has_dep = (pkg, name) => Boolean(R.view(R.lensPath(["dependencies", name]), pkg) ||
7
7
  R.view(R.lensPath(["devDependencies", name]), pkg));
@@ -13,7 +13,7 @@ const has_dep = (pkg, name) => Boolean(R.view(R.lensPath(["dependencies", name])
13
13
  const resolve_angular_output = (workdir) => {
14
14
  const fallback = "dist";
15
15
  try {
16
- const angular_json = fs.readJSONSync(path__default.join(workdir, "angular.json"));
16
+ const angular_json = read_json_file(path__default.join(workdir, "angular.json"));
17
17
  const projects = angular_json?.projects ?? {};
18
18
  const project_name = angular_json?.defaultProject ?? Object.keys(projects)[0];
19
19
  const build = projects?.[project_name]?.architect?.build;
@@ -1,7 +1,7 @@
1
- import fs from 'fs-extra';
2
1
  import path__default from 'path';
3
2
  import { cmd } from '../../../../lib/cmd.js';
4
3
  import { log } from '../../../../log.js';
4
+ import { read_json_file } from '../shared/read_text_file.js';
5
5
 
6
6
  const getCurrentNodeVersion = async () => {
7
7
  const out = await cmd(`node --version`);
@@ -16,7 +16,7 @@ const getCurrentNodeVersion = async () => {
16
16
  */
17
17
  const resolve_node_version = async (workdir) => {
18
18
  const packageJSONFile = path__default.join(workdir, "package.json");
19
- const { name, engines } = fs.readJSONSync(packageJSONFile);
19
+ const { name, engines } = read_json_file(packageJSONFile);
20
20
  if (!name) {
21
21
  throw new Error("Missing name in package.json");
22
22
  }
@@ -3,6 +3,7 @@ import path__default from 'path';
3
3
  import { log } from '../../../../log.js';
4
4
  import { render_dockerfile, build_image } from '../shared/docker_image.js';
5
5
  import { has_any_of_files } from '../shared/has_any_of_files.js';
6
+ import { read_text_file } from '../shared/read_text_file.js';
6
7
  import { cerebrium_provider } from './providers/cerebrium.js';
7
8
  import { pipfile_provider } from './providers/pipfile.js';
8
9
  import { pyproject_provider } from './providers/pyproject.js';
@@ -89,7 +90,7 @@ const python_buildpack = {
89
90
  // Framework detection blob = the entry files themselves: an
90
91
  // `from fastapi import FastAPI` line carries the token detection needs.
91
92
  const deps = entries
92
- .map((f) => fs.readFileSync(path__default.join(ctx.workdir, f)).toString())
93
+ .map((f) => read_text_file(path__default.join(ctx.workdir, f)))
93
94
  .join("\n")
94
95
  .toLowerCase();
95
96
  const { start_command, server } = resolve_start(ctx.workdir, deps, ctx.config);
@@ -1,5 +1,6 @@
1
1
  import fs from 'fs-extra';
2
2
  import path__default from 'path';
3
+ import { read_text_file } from '../shared/read_text_file.js';
3
4
 
4
5
  /**
5
6
  * Read the `web:` process command from a Procfile, if present. Returns null when
@@ -10,7 +11,7 @@ const parse_procfile = (workdir) => {
10
11
  const procfile = path__default.join(workdir, "Procfile");
11
12
  if (!fs.existsSync(procfile))
12
13
  return null;
13
- const content = fs.readFileSync(procfile).toString();
14
+ const content = read_text_file(procfile);
14
15
  for (const line of content.split("\n")) {
15
16
  const match = line.match(/^\s*web\s*:\s*(.+?)\s*$/);
16
17
  if (match?.[1])
@@ -1,5 +1,5 @@
1
- import fs from 'fs-extra';
2
1
  import path__default from 'path';
2
+ import { read_text_file } from '../../shared/read_text_file.js';
3
3
  import { parse_cerebrium_toml } from './parse_cerebrium_toml.js';
4
4
 
5
5
  /**
@@ -12,7 +12,7 @@ const cerebrium_provider = {
12
12
  name: "cerebrium",
13
13
  files: ["cerebrium.toml"],
14
14
  resolve(workdir) {
15
- const manifest = parse_cerebrium_toml(fs.readFileSync(path__default.join(workdir, "cerebrium.toml")).toString());
15
+ const manifest = parse_cerebrium_toml(read_text_file(path__default.join(workdir, "cerebrium.toml")));
16
16
  let install_command;
17
17
  let install_files;
18
18
  if (manifest.pip_requirements_file) {
@@ -1,5 +1,6 @@
1
1
  import fs from 'fs-extra';
2
2
  import path__default from 'path';
3
+ import { read_text_file } from '../shared/read_text_file.js';
3
4
 
4
5
  const DEFAULT_VERSION = "3.11.3";
5
6
  /**
@@ -13,7 +14,7 @@ const DEFAULT_VERSION = "3.11.3";
13
14
  const resolve_python_version = (workdir, provider_hint) => {
14
15
  const runtime_config = path__default.join(workdir, "runtime.txt");
15
16
  if (fs.existsSync(runtime_config)) {
16
- const runtime_data = fs.readFileSync(runtime_config).toString().trim();
17
+ const runtime_data = read_text_file(runtime_config).trim();
17
18
  if (!runtime_data.startsWith("python-")) {
18
19
  throw new Error("runtime.txt must have runtime format with python-<version>");
19
20
  }
@@ -21,7 +22,7 @@ const resolve_python_version = (workdir, provider_hint) => {
21
22
  }
22
23
  const python_version_file = path__default.join(workdir, ".python-version");
23
24
  if (fs.existsSync(python_version_file)) {
24
- const version = fs.readFileSync(python_version_file).toString().trim();
25
+ const version = read_text_file(python_version_file).trim();
25
26
  if (version)
26
27
  return version;
27
28
  }
@@ -29,7 +30,7 @@ const resolve_python_version = (workdir, provider_hint) => {
29
30
  return provider_hint;
30
31
  const pyproject = path__default.join(workdir, "pyproject.toml");
31
32
  if (fs.existsSync(pyproject)) {
32
- const toml = fs.readFileSync(pyproject).toString();
33
+ const toml = read_text_file(pyproject);
33
34
  const match = toml.match(/requires-python\s*=\s*["'][^0-9]*([0-9]+\.[0-9]+(?:\.[0-9]+)?)/);
34
35
  if (match?.[1])
35
36
  return match[1];
@@ -1,6 +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 { read_text_file } from '../shared/read_text_file.js';
4
5
  import { parse_procfile } from './parse_procfile.js';
5
6
 
6
7
  /** Combine all classic dependency manifests into one lowercased blob. */
@@ -9,7 +10,7 @@ const read_dependencies_text = (workdir) => {
9
10
  return files
10
11
  .map((f) => path__default.join(workdir, f))
11
12
  .filter((p) => fs.existsSync(p))
12
- .map((p) => fs.readFileSync(p).toString())
13
+ .map((p) => read_text_file(p))
13
14
  .join("\n")
14
15
  .toLowerCase();
15
16
  };
@@ -59,7 +60,7 @@ const find_app_module = (workdir, pattern) => {
59
60
  const module = rel.replace(/\.py$/, "").split(path__default.sep).join(".");
60
61
  if (!first_existing)
61
62
  first_existing = module;
62
- if (pattern.test(fs.readFileSync(abs).toString()))
63
+ if (pattern.test(read_text_file(abs)))
63
64
  return module;
64
65
  }
65
66
  return first_existing;
@@ -0,0 +1,31 @@
1
+ import fs from 'fs-extra';
2
+
3
+ /**
4
+ * Decode a manifest/source buffer to a string, honoring a leading BOM.
5
+ *
6
+ * Editors and shells on Windows (PowerShell `>` redirection, Notepad "Save As")
7
+ * frequently write files as UTF‑16 with a BOM. Reading those as UTF‑8 yields
8
+ * mojibake with interleaved NUL bytes (`f\x00a\x00s\x00t…`), which silently
9
+ * breaks token detection — e.g. `\bfastapi\b` never matches a UTF‑16
10
+ * `requirements.txt`, so the Python buildpack skips framework detection and
11
+ * fails with "Could not detect how to start this Python app". Detect the BOM and
12
+ * decode accordingly, stripping the BOM from the result.
13
+ */
14
+ const decode_buffer = (buf) => {
15
+ if (buf.length >= 2 && buf[0] === 0xff && buf[1] === 0xfe) {
16
+ return new TextDecoder("utf-16le").decode(buf.subarray(2));
17
+ }
18
+ if (buf.length >= 2 && buf[0] === 0xfe && buf[1] === 0xff) {
19
+ return new TextDecoder("utf-16be").decode(buf.subarray(2));
20
+ }
21
+ if (buf.length >= 3 && buf[0] === 0xef && buf[1] === 0xbb && buf[2] === 0xbf) {
22
+ return buf.subarray(3).toString("utf8");
23
+ }
24
+ return buf.toString("utf8");
25
+ };
26
+ /** Read a file to a string, decoding UTF‑16/UTF‑8 BOMs (see {@link decode_buffer}). */
27
+ const read_text_file = (file) => decode_buffer(fs.readFileSync(file));
28
+ /** Read and parse a JSON file, tolerating a UTF‑16/UTF‑8 BOM. */
29
+ const read_json_file = (file) => JSON.parse(read_text_file(file));
30
+
31
+ export { decode_buffer, read_json_file, read_text_file };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@faable/faable",
3
- "version": "1.15.1",
3
+ "version": "1.15.2",
4
4
  "main": "dist/index.js",
5
5
  "license": "MIT",
6
6
  "author": "Marc Pomar <marc@faable.com>",