@entur/function-tools 0.0.5 → 0.0.6

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.
@@ -5,7 +5,7 @@ import { registerStart } from '../lib/commands/start.js';
5
5
  import { registerUnusedExports } from '../lib/commands/unusedExports.js';
6
6
 
7
7
  const program = new Command();
8
- program.name("entur-functions").description("A multi-tool for Firebase functions at Entur").version("0.0.5").option("-v, --verbose", "Enable verbose output");
8
+ program.name("entur-functions").description("A multi-tool for Firebase functions at Entur").version("0.0.6").option("-v, --verbose", "Enable verbose output");
9
9
  registerBuild(program);
10
10
  registerDeploy(program);
11
11
  registerStart(program);
@@ -0,0 +1,88 @@
1
+ import { writeFile } from 'node:fs/promises';
2
+ import { builtinModules } from 'node:module';
3
+ import { dirname } from 'node:path';
4
+ import { stringify } from 'yaml';
5
+ import { groupByAsync, asyncMap } from '../utils/async.js';
6
+ import { spawnAsync } from '../utils/exec.js';
7
+ import { writePackageJSON, readPackageJSON, packageUp } from '../utils/packageJSON.js';
8
+
9
+ const alwaysIncludePackageNames = [
10
+ "@google-cloud/functions-framework",
11
+ "firebase-functions",
12
+ "firebase-admin"
13
+ ];
14
+ async function writeDependencies(outputs, getOutputFilePath, outputDir) {
15
+ const packages = [];
16
+ for (const [packageJSONPath, packageJSON] of (await calculateDependencies(outputs))){
17
+ const newPackageJSONPath = getOutputFilePath(packageJSONPath);
18
+ const directory = dirname(newPackageJSONPath);
19
+ if (directory === ".") {
20
+ await writePackageJSON(new URL(`./${newPackageJSONPath}`, outputDir), {
21
+ ...packageJSON,
22
+ main: `./${outputs[0].fileName}`
23
+ });
24
+ } else {
25
+ await writePackageJSON(new URL(`./${newPackageJSONPath}`, outputDir), packageJSON);
26
+ packages.push(directory);
27
+ }
28
+ }
29
+ await writeFile(new URL("./pnpm-workspace.yaml", outputDir), stringify({
30
+ packages
31
+ }));
32
+ }
33
+ async function calculateDependencies(outputs) {
34
+ const outputGroupedByPackage = await groupByAsync(outputs.filter((it)=>Boolean(it.type === "chunk" && it.facadeModuleId && it.imports.length > 0)), async (output)=>{
35
+ const key = output.facadeModuleId && await packageUp({
36
+ cwd: output.facadeModuleId
37
+ });
38
+ if (!key) {
39
+ throw new Error(`No package.json found for ${output.facadeModuleId}`);
40
+ }
41
+ return key;
42
+ });
43
+ const filterDependencies = (lookup, dependencies)=>{
44
+ if (!dependencies) return;
45
+ const entries = Object.entries(dependencies).filter(([key])=>lookup.has(key));
46
+ return entries.length > 0 ? Object.fromEntries(entries) : undefined;
47
+ };
48
+ const entries = await asyncMap([
49
+ ...outputGroupedByPackage
50
+ ], async ([packageJSONPath, outputs])=>{
51
+ const { name, type, version, ...packageJSON } = await readPackageJSON(packageJSONPath);
52
+ const importedPackageNames = new Set(outputs.flatMap((it)=>it.imports).filter((it)=>!isBuiltinImport(it)).map(specifierToPackageName));
53
+ alwaysIncludePackageNames?.forEach((it)=>{
54
+ importedPackageNames.add(it);
55
+ });
56
+ return [
57
+ packageJSONPath,
58
+ {
59
+ name,
60
+ type,
61
+ version,
62
+ dependencies: filterDependencies(importedPackageNames, packageJSON.dependencies),
63
+ devDependencies: filterDependencies(importedPackageNames, packageJSON.devDependencies),
64
+ optionalDependencies: filterDependencies(importedPackageNames, packageJSON.optionalDependencies)
65
+ }
66
+ ];
67
+ });
68
+ return new Map(entries);
69
+ }
70
+ function specifierToPackageName(specifier) {
71
+ if (specifier.startsWith("@")) {
72
+ return specifier.split("/", 2).join("/");
73
+ }
74
+ return specifier.split("/", 1).join("/");
75
+ }
76
+ function isBuiltinImport(specifier) {
77
+ return specifier.startsWith("node:") || builtinModules.includes(specifier);
78
+ }
79
+ async function linkDependencies(workingDir) {
80
+ await spawnAsync("pnpm", [
81
+ "install",
82
+ "--prefer-offline"
83
+ ], {
84
+ cwd: workingDir
85
+ });
86
+ }
87
+
88
+ export { linkDependencies, writeDependencies };
@@ -2,29 +2,35 @@ import { fileURLToPath } from 'node:url';
2
2
  import { nodeResolve } from '@rollup/plugin-node-resolve';
3
3
  import swc from '@rollup/plugin-swc';
4
4
  import { rollup, watch } from 'rollup';
5
+ import { createOutputFileName } from './utils.js';
5
6
 
6
- async function bundle(entryFile, outputDir, { packagesToInline } = {}) {
7
- const bundle1 = await rollup({
7
+ async function bundle(entryFile, { outputDir, packageRoot, projectRoot, packagesToInline }) {
8
+ const bundle = await rollup({
8
9
  input: fileURLToPath(entryFile),
9
10
  plugins: plugins(packagesToInline),
10
11
  treeshake: "smallest"
11
12
  });
12
- return bundle1.write({
13
- ...outputOptions(),
14
- sourcemap: true,
15
- dir: fileURLToPath(outputDir)
13
+ const outputFileName = createOutputFileName(packageRoot, projectRoot);
14
+ return bundle.write({
15
+ preserveModules: true,
16
+ format: "esm",
17
+ dir: fileURLToPath(outputDir),
18
+ entryFileNames: ({ facadeModuleId })=>outputFileName(facadeModuleId)
16
19
  });
17
20
  }
18
- async function bundleAndWatch(entryFile, outputDir, { packagesToInline, onBundleEnd }) {
21
+ async function bundleAndWatch(entryFile, { outputDir, packageRoot, projectRoot, packagesToInline, onBundleEnd }) {
22
+ const outputFileName = createOutputFileName(packageRoot, projectRoot);
23
+ const output = {
24
+ preserveModules: true,
25
+ format: "esm",
26
+ dir: fileURLToPath(outputDir),
27
+ entryFileNames: ({ facadeModuleId })=>outputFileName(facadeModuleId)
28
+ };
19
29
  const watchOptions = {
20
30
  plugins: plugins(packagesToInline),
21
31
  input: fileURLToPath(entryFile),
22
32
  treeshake: "smallest",
23
- output: {
24
- ...outputOptions(),
25
- sourcemap: true,
26
- dir: fileURLToPath(outputDir)
27
- },
33
+ output,
28
34
  watch: {
29
35
  exclude: [
30
36
  fileURLToPath(outputDir)
@@ -41,9 +47,9 @@ async function bundleAndWatch(entryFile, outputDir, { packagesToInline, onBundle
41
47
  }
42
48
  case "BUNDLE_END":
43
49
  {
44
- const output = await event.result.generate(outputOptions());
50
+ const bundledOutput = await event.result.generate(output);
45
51
  console.log(`🏠 Finished build in ${event.duration}ms`);
46
- await onBundleEnd?.(output);
52
+ await onBundleEnd?.(bundledOutput);
47
53
  return event.result.close();
48
54
  }
49
55
  case "ERROR":
@@ -67,11 +73,5 @@ const plugins = (workspacePackages)=>[
67
73
  resolveOnly: (moduleId)=>moduleId.startsWith(".") || workspacePackages?.some((it)=>moduleId.startsWith(it)) || moduleId.startsWith("@entur-private/") || false
68
74
  })
69
75
  ];
70
- const outputOptions = ()=>({
71
- preserveModules: true,
72
- dir: "lib",
73
- format: "esm",
74
- entryFileNames: ({ isEntry })=>isEntry ? "index.js" : "[hash].js"
75
- });
76
76
 
77
77
  export { bundle, bundleAndWatch };
@@ -0,0 +1,23 @@
1
+ import { fileURLToPath } from 'node:url';
2
+
3
+ const nodeModulesPartPath = "/node_modules/";
4
+ function createOutputFileName(packageRootURL, projectRootURL) {
5
+ const projectRoot = fileURLToPath(projectRootURL);
6
+ const packageRoot = fileURLToPath(packageRootURL);
7
+ return (facadeModuleId)=>{
8
+ const index = facadeModuleId?.lastIndexOf(nodeModulesPartPath);
9
+ if (facadeModuleId && index && index > -1) {
10
+ return `bundled_modules/${facadeModuleId.slice(index + nodeModulesPartPath.length)}`;
11
+ }
12
+ const filePath = facadeModuleId?.replace(".ts", ".js").replaceAll("/src/", "/lib/");
13
+ if (filePath?.startsWith(packageRoot)) {
14
+ return filePath.slice(packageRoot.length);
15
+ }
16
+ if (filePath?.startsWith(projectRoot)) {
17
+ return `local_modules/${filePath.slice(projectRoot.length)}`;
18
+ }
19
+ throw new Error(`Unable to determine correct placement for file ${facadeModuleId}`);
20
+ };
21
+ }
22
+
23
+ export { createOutputFileName };
@@ -1,5 +1,6 @@
1
- import { bundle } from '../utils/bundle.js';
2
- import { flattenDependencies, harmonizeDependencies, calculateDependencies } from '../utils/dependencies.js';
1
+ import { writeDependencies } from '../bundle/dependencies.js';
2
+ import { bundle } from '../bundle/index.js';
3
+ import { createOutputFileName } from '../bundle/utils.js';
3
4
  import { cleanDir } from '../utils/fs.js';
4
5
  import { readPackageJSON } from '../utils/packageJSON.js';
5
6
  import { getWorkspacePackageNames } from '../utils/workspace.js';
@@ -8,31 +9,29 @@ import { resolveProjectConfig } from './utils.js';
8
9
  function registerBuild(program) {
9
10
  program.command("build").description("Build the project").option("-o, --output-dir <dir>", "Output directory").action(async (options)=>{
10
11
  try {
11
- const { packageRoot, packageJSON, outputDir, pnpmWorkspaceYAML } = await resolveProjectConfig(options);
12
+ const projectConfig = await resolveProjectConfig(options);
13
+ const { packageRoot, packageJSON, outputDir, pnpmWorkspaceYAML } = projectConfig;
12
14
  const { name, exports: exports$1 } = await readPackageJSON(packageJSON);
13
15
  console.log("🧹 Cleaning dist folder");
14
16
  await cleanDir(outputDir);
15
17
  console.log(`🔨 Building ${name}`);
16
18
  const workspacePackages = await getWorkspacePackageNames(pnpmWorkspaceYAML);
17
19
  const entryFile = new URL(exports$1?.["."] ?? "./index.js", packageRoot);
18
- await build(entryFile, outputDir, workspacePackages);
20
+ await build(entryFile, workspacePackages, projectConfig);
19
21
  } catch (error) {
20
22
  console.error(error);
21
23
  process.exit(1);
22
24
  }
23
25
  });
24
26
  }
25
- async function build(entryFile, outputDir, packagesToInline) {
26
- const bundleOutputDir = new URL("lib", outputDir);
27
- const { output } = await bundle(entryFile, bundleOutputDir, {
27
+ async function build(entryFile, packagesToInline, { outputDir, packageRoot, projectRoot }) {
28
+ const { output } = await bundle(entryFile, {
29
+ outputDir,
30
+ packageRoot,
31
+ projectRoot,
28
32
  packagesToInline
29
33
  });
30
- const dependencies = await flattenDependencies(harmonizeDependencies(await calculateDependencies(output)));
31
- // const { contentHash } = await pack(functionGroupDir, distDir)
32
- return {
33
- main: `./lib/${output[0].fileName}`,
34
- dependencies
35
- };
34
+ await writeDependencies(output, createOutputFileName(packageRoot, projectRoot), outputDir);
36
35
  }
37
36
 
38
37
  export { registerBuild };
@@ -1,9 +1,10 @@
1
- import { bundle } from '../utils/bundle.js';
2
- import { flattenDependencies, harmonizeDependencies, calculateDependencies, linkDependencies } from '../utils/dependencies.js';
3
- import { spawnAsync } from '../utils/exec.js';
1
+ import { writeDependencies, linkDependencies } from '../bundle/dependencies.js';
2
+ import { bundle } from '../bundle/index.js';
3
+ import { createOutputFileName } from '../bundle/utils.js';
4
+ import { deploy as deploy$1 } from '../firebase/index.js';
4
5
  import { getFirebaseJSON, writeFirebaseJSON } from '../utils/firebase.js';
5
6
  import { cleanDir, writeJSON } from '../utils/fs.js';
6
- import { readPackageJSON, getPackageName, writePackageJSON } from '../utils/packageJSON.js';
7
+ import { readPackageJSON, getPackageName } from '../utils/packageJSON.js';
7
8
  import { getWorkspacePackageNames } from '../utils/workspace.js';
8
9
  import { resolveProjectConfig, copyEnvFiles } from './utils.js';
9
10
 
@@ -20,47 +21,36 @@ function registerDeploy(program) {
20
21
  }
21
22
  async function deploy(projectConfig) {
22
23
  const { projectAlias, projectId, packageJSON, packageRoot, pnpmWorkspaceYAML, projectRoot, outputDir } = projectConfig;
23
- const { name, version, exports: exports$1 } = await readPackageJSON(packageJSON);
24
+ const { name, exports: exports$1 } = await readPackageJSON(packageJSON);
24
25
  const workspacePackages = await getWorkspacePackageNames(pnpmWorkspaceYAML);
25
26
  const entryFile = new URL(exports$1?.["."] ?? "./index.js", packageRoot);
26
27
  console.log("🧹 Cleaning dist folder");
27
28
  await cleanDir(outputDir);
28
29
  console.log(`🔨 Building ${name}`);
29
- const { main, dependencies } = await build(entryFile, outputDir, workspacePackages);
30
+ await build(entryFile, workspacePackages, projectConfig);
30
31
  console.log("⛷️ Prepare deploy");
31
32
  const firebaseJSON = await getFirebaseJSON(new URL("firebase.json", projectRoot));
32
- await prepareDeploy(getPackageName(name), {
33
- name,
34
- type: "module",
35
- version,
36
- main,
37
- ...dependencies
38
- }, firebaseJSON, projectConfig);
33
+ await prepareDeploy(getPackageName(name), firebaseJSON, projectConfig);
39
34
  console.log(`🚢 Deploying to ${projectAlias} (${projectId})`);
40
- await deployToFirebase(outputDir, projectId);
35
+ await deploy$1({
36
+ projectId,
37
+ cwd: outputDir
38
+ });
41
39
  }
42
- async function build(entryFile, outputDir, packagesToInline) {
43
- const bundleOutputDir = new URL("lib", outputDir);
44
- const { output } = await bundle(entryFile, bundleOutputDir, {
40
+ async function build(entryFile, packagesToInline, { outputDir, packageRoot, projectRoot }) {
41
+ const { output } = await bundle(entryFile, {
42
+ packageRoot,
43
+ projectRoot,
44
+ outputDir,
45
45
  packagesToInline
46
46
  });
47
- const dependencies = await flattenDependencies(harmonizeDependencies(await calculateDependencies(output)));
48
- // const { contentHash } = await pack(functionGroupDir, distDir)
49
- return {
50
- main: `./lib/${output[0].fileName}`,
51
- dependencies
52
- };
47
+ await writeDependencies(output, createOutputFileName(packageRoot, projectRoot), outputDir);
53
48
  }
54
- async function prepareDeploy(codebase, packageJSON, firebaseJSON, { projectAlias, projectId, outputDir, envFiles }) {
55
- const envPrefix = `
56
- NODE_OPTIONS='--enable-source-maps'
57
- FUNCTION_CODEBASE='${codebase}'
58
- ENTUR_PROJECT_ALIAS='${projectAlias}'
59
- `.trimStart();
49
+ async function prepareDeploy(codebase, firebaseJSON, { projectAlias, projectId, outputDir, envFiles }) {
50
+ const envPrefix = `FUNCTION_CODEBASE='${codebase}'\nENTUR_PROJECT_ALIAS='${projectAlias}'`;
60
51
  await Promise.all([
61
52
  writeFirebaseJSON(codebase, firebaseJSON, outputDir),
62
53
  createFirebaseRC(projectAlias, projectId, outputDir),
63
- writePackageJSON(new URL("./package.json", outputDir), packageJSON),
64
54
  copyEnvFiles(envFiles, envPrefix, outputDir)
65
55
  ]);
66
56
  await linkDependencies(outputDir);
@@ -72,17 +62,5 @@ async function createFirebaseRC(projectAlias, projectId, outputDir) {
72
62
  }
73
63
  });
74
64
  }
75
- function deployToFirebase(workingDir, projectId, extraArgs = []) {
76
- return spawnAsync("firebase", [
77
- "deploy",
78
- "--only",
79
- "functions,firestore",
80
- "-P",
81
- projectId,
82
- ...extraArgs
83
- ], {
84
- cwd: workingDir
85
- });
86
- }
87
65
 
88
66
  export { registerDeploy };
@@ -1,72 +1,65 @@
1
- import { bundleAndWatch } from '../utils/bundle.js';
2
- import { flattenDependencies, harmonizeDependencies, calculateDependencies, linkDependencies } from '../utils/dependencies.js';
3
- import { spawnAsync } from '../utils/exec.js';
1
+ import { writeDependencies, linkDependencies } from '../bundle/dependencies.js';
2
+ import { bundleAndWatch } from '../bundle/index.js';
3
+ import { createOutputFileName } from '../bundle/utils.js';
4
+ import { startEmulator } from '../firebase/index.js';
4
5
  import { getFirebaseJSON, writeFirebaseJSON } from '../utils/firebase.js';
5
6
  import { cleanDir, writeJSON } from '../utils/fs.js';
6
- import { readPackageJSON, getPackageName, writePackageJSON } from '../utils/packageJSON.js';
7
+ import { readPackageJSON, getPackageName } from '../utils/packageJSON.js';
7
8
  import { getWorkspacePackageNames } from '../utils/workspace.js';
8
9
  import { resolveProjectConfig, copyEnvFiles } from './utils.js';
9
10
 
10
11
  function registerStart(program) {
11
- program.command("start").description("Start function emulator").option("-o, --output-dir <dir>", "Output directory").option("-P, --project <project id or alias>", "Project id or alias").action(async (options)=>{
12
+ program.command("start").description("Start function emulator").option("-o, --output-dir <dir>", "Output directory").option("-P, --project <project id or alias>", "Project id or alias").option("--inspect-functions [port]", "emulate Cloud Functions in debug mode with the node inspector on the given port (9229 if not specified)").action(async (options)=>{
12
13
  try {
13
14
  const projectConfig = await resolveProjectConfig({
14
15
  ...options,
15
16
  isEmulator: true
16
17
  });
17
- await start(projectConfig);
18
+ await start(options.inspectFunctions ?? false, projectConfig);
18
19
  } catch (error) {
19
20
  console.error(error);
20
21
  process.exit(1);
21
22
  }
22
23
  });
23
24
  }
24
- async function start(projectConfig) {
25
- const { packageRoot, packageJSON, projectAlias, projectId, projectRoot, pnpmWorkspaceYAML, outputDir } = projectConfig;
26
- const { name, version, exports: exports$1 } = await readPackageJSON(packageJSON);
25
+ async function start(inspectFunctions, projectConfig) {
26
+ const { packageRoot, packageJSON, projectId, projectRoot, pnpmWorkspaceYAML, outputDir } = projectConfig;
27
+ const { name, exports: exports$1 } = await readPackageJSON(packageJSON);
27
28
  const codebase = getPackageName(name);
28
29
  const packagesToInline = await getWorkspacePackageNames(pnpmWorkspaceYAML);
29
30
  const entryFile = new URL(exports$1?.["."] ?? "./index.js", packageRoot);
30
31
  console.log("🧹 Cleaning dist folder");
31
32
  await cleanDir(outputDir);
32
33
  console.log(`🔨 Building ${name}`);
33
- const bundleOutputDir = new URL("lib", outputDir);
34
34
  let firstRun = true;
35
35
  const onBundleEnd = async ({ output })=>{
36
- const main = `./lib/${output[0].fileName}`;
37
- const dependencies = await flattenDependencies(harmonizeDependencies(await calculateDependencies(output)));
38
- // const { contentHash } = await pack(functionGroupDir, distDir)
36
+ await writeDependencies(output, createOutputFileName(packageRoot, projectRoot), outputDir);
39
37
  const firebaseJSON = await getFirebaseJSON(new URL("firebase.json", projectRoot));
40
- await prepareStart(codebase, {
41
- name,
42
- type: "module",
43
- version,
44
- main,
45
- ...dependencies
46
- }, firebaseJSON, projectConfig);
38
+ await prepareStart(codebase, firebaseJSON, projectConfig);
47
39
  if (firstRun) {
48
40
  firstRun = false;
49
- runEmulator(codebase, projectAlias, projectId, outputDir, []).catch((error)=>{
41
+ startEmulator({
42
+ projectId,
43
+ cwd: outputDir,
44
+ inspectFunctions
45
+ }).catch((error)=>{
50
46
  console.error(error);
51
- process.exit(1);
52
47
  });
53
48
  }
54
49
  };
55
- await bundleAndWatch(entryFile, bundleOutputDir, {
50
+ await bundleAndWatch(entryFile, {
51
+ outputDir,
52
+ projectRoot,
53
+ packageRoot,
56
54
  packagesToInline,
57
55
  onBundleEnd
58
56
  });
59
57
  }
60
- async function prepareStart(codebase, packageJSON, firebaseJSON, { projectAlias, projectId, outputDir, envFiles }) {
61
- const envPrefix = `
62
- NODE_OPTIONS='--enable-source-maps'
63
- FUNCTION_CODEBASE='${codebase}'
64
- ENTUR_PROJECT_ALIAS='${projectAlias}'
65
- `.trimStart();
58
+ async function prepareStart(codebase, firebaseJSON, { projectAlias, projectId, outputDir, envFiles }) {
59
+ const envPrefix = `FUNCTION_CODEBASE='${codebase}'\nENTUR_PROJECT_ALIAS='${projectAlias}'`;
66
60
  await Promise.all([
67
61
  writeFirebaseJSON(codebase, firebaseJSON, outputDir),
68
62
  createFirebaseRC(projectAlias, projectId, outputDir),
69
- writePackageJSON(new URL("./package.json", outputDir), packageJSON),
70
63
  copyEnvFiles(envFiles, envPrefix, outputDir)
71
64
  ]);
72
65
  await linkDependencies(outputDir);
@@ -78,23 +71,5 @@ async function createFirebaseRC(projectAlias, projectId, outputDir) {
78
71
  }
79
72
  });
80
73
  }
81
- function runEmulator(codebase, projectAlias, projectId, workingDir, extraArgs) {
82
- return spawnAsync("firebase", [
83
- "emulators:start",
84
- "--only",
85
- "functions",
86
- "-P",
87
- projectId,
88
- ...extraArgs
89
- ], {
90
- cwd: workingDir,
91
- env: {
92
- ...process.env,
93
- FUNCTION_CODEBASE: codebase,
94
- ENTUR_PROJECT_ALIAS: projectAlias,
95
- NODE_OPTIONS: process.env.NODE_OPTIONS ? `${process.env.NODE_OPTIONS} --enable-source-maps` : "--enable-source-maps"
96
- }
97
- });
98
- }
99
74
 
100
75
  export { registerStart };
@@ -95,7 +95,7 @@ async function copyEnvFiles([envFile, ...envFiles], prefix, outputDir) {
95
95
  if (envFile) {
96
96
  const fileContent = await readFile(envFile, "utf8");
97
97
  const fileName = basename(fileURLToPath(envFile));
98
- await writeFile(new URL(`./${fileName}`, outputDir), prefix.trimStart() + fileContent);
98
+ await writeFile(new URL(`./${fileName}`, outputDir), `${prefix}\n${fileContent}`);
99
99
  } else {
100
100
  await writeFile(new URL("./.env", outputDir), prefix);
101
101
  }
@@ -0,0 +1,47 @@
1
+ import { spawnAsync } from '../utils/exec.js';
2
+
3
+ const FUNCTIONS_DISCOVERY_TIMEOUT = "30";
4
+ function deploy({ projectId, cwd }) {
5
+ return spawnAsync("firebase", [
6
+ "deploy",
7
+ "--only",
8
+ "functions,firestore",
9
+ "-P",
10
+ projectId
11
+ ], {
12
+ cwd,
13
+ env: {
14
+ ...process.env,
15
+ FUNCTIONS_DISCOVERY_TIMEOUT
16
+ }
17
+ });
18
+ }
19
+ function startEmulator({ projectId, cwd, inspectFunctions }) {
20
+ let extraArgs = [];
21
+ if (typeof inspectFunctions === "string") {
22
+ extraArgs = [
23
+ "--inspect-functions",
24
+ inspectFunctions
25
+ ];
26
+ } else if (inspectFunctions) {
27
+ extraArgs = [
28
+ "--inspect-functions"
29
+ ];
30
+ }
31
+ return spawnAsync("firebase", [
32
+ "emulators:start",
33
+ "--only",
34
+ "functions",
35
+ "-P",
36
+ projectId,
37
+ ...extraArgs
38
+ ], {
39
+ cwd,
40
+ env: {
41
+ ...process.env,
42
+ FUNCTIONS_DISCOVERY_TIMEOUT
43
+ }
44
+ });
45
+ }
46
+
47
+ export { deploy, startEmulator };
@@ -12,20 +12,6 @@ function filterMap(arr, mapper) {
12
12
  return acc;
13
13
  }, []);
14
14
  }
15
- function groupBy(array, iteratee) {
16
- return array.reduce((map, item, index, array_)=>{
17
- const key = iteratee(item, index, array_);
18
- const existingGroup = map.get(key);
19
- if (existingGroup) {
20
- existingGroup.push(item);
21
- } else {
22
- map.set(key, [
23
- item
24
- ]);
25
- }
26
- return map;
27
- }, new Map());
28
- }
29
15
  function partition(array, predicate) {
30
16
  return array.reduce((result, value, index, arr)=>{
31
17
  const [include, exclude] = result;
@@ -41,4 +27,4 @@ function partition(array, predicate) {
41
27
  ]);
42
28
  }
43
29
 
44
- export { filterMap, groupBy, partition, uniq };
30
+ export { filterMap, partition, uniq };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@entur/function-tools",
3
3
  "type": "module",
4
- "version": "0.0.5",
4
+ "version": "0.0.6",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -21,7 +21,6 @@
21
21
  "oxc-resolver": "^11.16.2",
22
22
  "rollup": "^4.54.0",
23
23
  "rollup-plugin-dts": "^6.3.0",
24
- "semver": "^7.7.3",
25
24
  "yaml": "^2.8.2"
26
25
  },
27
26
  "peerDependencies": {
@@ -1,159 +0,0 @@
1
- import { builtinModules } from 'node:module';
2
- import { dirname } from 'node:path';
3
- import { coerce, satisfies } from 'semver';
4
- import { groupBy } from './array.js';
5
- import { groupByAsync, asyncMap } from './async.js';
6
- import { spawnAsync } from './exec.js';
7
- import { readPackageJSON, packageUp } from './packageJSON.js';
8
-
9
- const alwaysIncludePackageNames = [
10
- "@google-cloud/functions-framework"
11
- ];
12
- async function calculateDependencies(outputs) {
13
- const outputGroupedByPackage = await groupByAsync(outputs.filter((it)=>Boolean(it.type === "chunk" && it.facadeModuleId && it.imports.length > 0)), async (output)=>{
14
- const key = output.facadeModuleId && await packageUp({
15
- cwd: output.facadeModuleId
16
- });
17
- if (!key) {
18
- throw new Error(`No package.json found for ${output.facadeModuleId}`);
19
- }
20
- return key;
21
- });
22
- const filterDependencies = (lookup, dependencies)=>{
23
- if (!dependencies) return;
24
- const entries = Object.entries(dependencies).filter(([key])=>lookup.has(key));
25
- return Object.fromEntries(entries);
26
- };
27
- const entries = await asyncMap([
28
- ...outputGroupedByPackage
29
- ], async ([packageJSONPath, outputs])=>{
30
- const packageJSON = await readPackageJSON(packageJSONPath);
31
- const importedPackageNames = new Set(outputs.flatMap((it)=>it.imports).filter((it)=>!isBuiltinImport(it)).map(specifierToPackageName));
32
- if (alwaysIncludePackageNames?.length) {
33
- alwaysIncludePackageNames.forEach((it)=>{
34
- importedPackageNames.add(it);
35
- });
36
- }
37
- return [
38
- dirname(packageJSONPath),
39
- {
40
- ...packageJSON,
41
- dependencies: filterDependencies(importedPackageNames, packageJSON.dependencies),
42
- devDependencies: filterDependencies(importedPackageNames, packageJSON.devDependencies),
43
- optionalDependencies: filterDependencies(importedPackageNames, packageJSON.optionalDependencies)
44
- }
45
- ];
46
- });
47
- return new Map(entries);
48
- }
49
- function specifierToPackageName(specifier) {
50
- if (specifier.startsWith("@")) {
51
- return specifier.split("/", 2).join("/");
52
- }
53
- return specifier.split("/", 1).join("/");
54
- }
55
- function isBuiltinImport(specifier) {
56
- return builtinModules.includes(specifier) || specifier.startsWith("node:");
57
- }
58
- function harmonizeDependencies(initialDependecies) {
59
- const entries = [
60
- ...initialDependecies.values()
61
- ].flatMap((it)=>[
62
- ...Object.entries(it.dependencies ?? {}),
63
- ...Object.entries(it.devDependencies ?? {}),
64
- ...Object.entries(it.peerDependencies ?? {}),
65
- ...Object.entries(it.optionalDependencies ?? {})
66
- ]);
67
- const harmonizedSemvers = new Map([
68
- ...groupBy(entries, ([packageName])=>packageName)
69
- ].map(([packageName, entriesForPackageName])=>{
70
- const firstVersion = entriesForPackageName[0][1];
71
- if (entriesForPackageName.length === 1) {
72
- return [
73
- packageName,
74
- firstVersion
75
- ];
76
- }
77
- if (entriesForPackageName.every(([, semver])=>semver === firstVersion)) {
78
- return [
79
- packageName,
80
- firstVersion
81
- ];
82
- }
83
- const satisfiedVersion = entriesForPackageName.reduce((acc, [, semver])=>{
84
- if (acc === semver) {
85
- return acc;
86
- }
87
- const semVerOrNull = coerce(semver);
88
- if (semVerOrNull !== null && satisfies(semVerOrNull, acc)) {
89
- console.log(`${packageName} ${semver} over ${acc}`);
90
- return semver;
91
- }
92
- if (semVerOrNull !== null && satisfies(semVerOrNull, semver)) {
93
- console.log(`${packageName} ${acc} over ${semver}`);
94
- return acc;
95
- }
96
- throw new Error(`Unable to find determine correct version of ${packageName} between ${semver} and ${acc}`);
97
- }, firstVersion);
98
- return [
99
- packageName,
100
- satisfiedVersion
101
- ];
102
- }));
103
- const harmonizeSemver = (dependencies)=>{
104
- if (!dependencies) return;
105
- const entries = Object.entries(dependencies).map(([packageName, initialSemver])=>{
106
- const semver = harmonizedSemvers.get(packageName);
107
- return [
108
- packageName,
109
- semver ?? initialSemver
110
- ];
111
- });
112
- return Object.fromEntries(entries);
113
- };
114
- return new Map([
115
- ...initialDependecies
116
- ].map(([packageName, packageJSON])=>{
117
- return [
118
- packageName,
119
- {
120
- ...packageJSON,
121
- dependencies: harmonizeSemver(packageJSON.dependencies),
122
- devDependencies: harmonizeSemver(packageJSON.devDependencies),
123
- peerDependencies: harmonizeSemver(packageJSON.peerDependencies)
124
- }
125
- ];
126
- }));
127
- }
128
- // optionalDependencies: harmonizeSemver(packageJSON.optionalDependencies),
129
- async function flattenDependencies(dependencies) {
130
- const listOfDependencies = [
131
- ...dependencies.values()
132
- ].map((it)=>it.dependencies);
133
- const listOfDevDependencies = [
134
- ...dependencies.values()
135
- ].map((it)=>it.devDependencies);
136
- const listOfPeerDependencies = [
137
- ...dependencies.values()
138
- ].map((it)=>it.peerDependencies);
139
- const listOfOptionalDependencies = [
140
- ...dependencies.values()
141
- ].map((it)=>it.optionalDependencies);
142
- return {
143
- dependencies: Object.assign({}, ...listOfDependencies),
144
- devDependencies: Object.assign({}, ...listOfDevDependencies),
145
- peerDependencies: Object.assign({}, ...listOfPeerDependencies),
146
- optionalDependencies: Object.assign({}, ...listOfOptionalDependencies)
147
- };
148
- }
149
- async function linkDependencies(workingDir) {
150
- await spawnAsync("pnpm", [
151
- "install",
152
- "--ignore-workspace",
153
- "--prefer-offline"
154
- ], {
155
- cwd: workingDir
156
- });
157
- }
158
-
159
- export { calculateDependencies, flattenDependencies, harmonizeDependencies, linkDependencies };