Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cloudflare Build Error #204

Open
1 task
moishinetzer opened this issue May 15, 2024 · 7 comments
Open
1 task

Cloudflare Build Error #204

moishinetzer opened this issue May 15, 2024 · 7 comments

Comments

@moishinetzer
Copy link

Running OS

MacOS

Running Node Version

🤷 workerd I guess? It's the cloudflare build run time (I use bun)

Description

Build works locally but on deployed app (Cloudflare pages) does not work. See the following error:

20:16:09.742 | ✘ [ERROR] Build failed with 5 errors:
-- | --
20:16:09.742 |  
20:16:09.742 | ../../node_modules/passkit-generator/lib/Bundle.js:6:25: ERROR: Could not resolve "stream"
20:16:09.743 | ../../node_modules/passkit-generator/lib/PKPass.js:6:47: ERROR: Could not resolve "path"
20:16:09.743 | ../../node_modules/passkit-generator/lib/StringsUtils.js:4:21: ERROR: Could not resolve "os"
20:16:09.743 | ../../node_modules/passkit-generator/lib/getModelFolderContents.js:4:42: ERROR: Could not resolve "path"
20:16:09.743 | ../../node_modules/passkit-generator/lib/getModelFolderContents.js:7:21: ERROR: Could not resolve "fs"

It is because of the usage of "stream" and other node api's without the node ("node:stream") prefix. I believe even if this would be changed in the code, this would still be an issue because the node prefix does not work with require and only works with import. This package is built to use require.


Expected behavior

Project builds fine on cloudflare

Steps to reproduce

Were you able to verify it by using (and changing) the examples?

If yes, which changes did you apply?

Other details

  • I'm available to open a Pull Request to resolve the problem (after the triage)
@alexandercerutti
Copy link
Owner

alexandercerutti commented May 15, 2024

Hey @moishinetzer, thanks for using passkit-generator!

node: prefix works for both import() and require(). Look at it here: https://nodejs.org/api/modules.html#core-modules

Did you follow the example to run the code on a Cloudflare worker? I think the issue could be that Workers by default do not support Node.JS API, but they must be told to do so.

Did you added this to your wrangler.toml file?

#########################################################################################
# This must be enabled to make passkit-generator compatible with cloudflare workers #
#########################################################################################
node_compat = true

Let me know!

P.s. I'm planning a rewrite to be fully compatible with ESM

@moishinetzer
Copy link
Author

I do have the flag enabled yet it is still not working for me.

I am using a remix app deployed to cloudflare pages, see my code below:

import pkg from "passkit-generator";
const { PKPass } = pkg;
import { Buffer } from "node:buffer";
import { LoaderFunctionArgs } from "@remix-run/cloudflare";

export interface Env {
  /**
   * "var" (instead of let and cost) is required here
   * to make typescript mark that these global variables
   * are available also in globalThis.
   *
   * These are secrets we have defined through `wrangler secret put <var name>`.
   * @see https://developers.cloudflare.com/workers/platform/environment-variables
   */

  WWDR: string;
  /** Pass signerCert */
  SIGNER_CERT: string;
  /** Pass signerKey */
  SIGNER_KEY: string;
  SIGNER_PASSPHRASE: string;
}

/**
 * Request entry point
 */

export async function loader({ request, context }: LoaderFunctionArgs) {
  const url = new URL(request.url);
  const origin = url.origin;

  const env = context.cloudflare.env as unknown as Env;

  const [icon, icon2x, footer, footer2x, background2x] = await Promise.all([
    fetch(`${origin}/footer.png`).then((res) => res.arrayBuffer()),
    fetch(`${origin}/[email protected]`).then((res) => res.arrayBuffer()),
    fetch(`${origin}/footer.png`).then((res) => res.arrayBuffer()),
    fetch(`${origin}/[email protected]`).then((res) => res.arrayBuffer()),
    fetch(`${origin}/[email protected]`).then((res) => res.arrayBuffer()),
  ]);

  const pass = new PKPass(
    {
      "icon.png": Buffer.from(icon),
      "[email protected]": Buffer.from(icon2x),
      "footer.png": Buffer.from(footer),
      "[email protected]": Buffer.from(footer2x),
      "[email protected]": Buffer.from(background2x),
    },
    {
      signerCert: env.SIGNER_CERT,
      signerKey: env.SIGNER_KEY,
      signerKeyPassphrase: env.SIGNER_PASSPHRASE,
      wwdr: env.WWDR,
    },
    {
      description: "Example Pass generated through a cloudflare worker",
      serialNumber: "123456",
      passTypeIdentifier: "pass.testpass",
      teamIdentifier: "teamid",
      organizationName: "testpass",
      foregroundColor: "rgb(255, 255, 255)",
      backgroundColor: "rgb(60, 65, 76)",
    }
  );

  pass.setBarcodes("1276451828321");
  pass.type = "boardingPass";
  pass.transitType = "PKTransitTypeAir";

  pass.headerFields.push(
    {
      key: "header1",
      label: "Data",
      value: "25 mag",
      textAlignment: "PKTextAlignmentCenter",
    },
    {
      key: "header2",
      label: "Volo",
      value: "EZY997",
      textAlignment: "PKTextAlignmentCenter",
    }
  );

  pass.primaryFields.push(
    {
      key: "IATA-source",
      value: "NAP",
      label: "Napoli",
      textAlignment: "PKTextAlignmentLeft",
    },
    {
      key: "IATA-destination",
      value: "VCE",
      label: "Venezia Marco Polo",
      textAlignment: "PKTextAlignmentRight",
    }
  );

  pass.secondaryFields.push(
    {
      key: "secondary1",
      label: "Imbarco chiuso",
      value: "18:40",
      textAlignment: "PKTextAlignmentCenter",
    },
    {
      key: "sec2",
      label: "Partenze",
      value: "19:10",
      textAlignment: "PKTextAlignmentCenter",
    },
    {
      key: "sec3",
      label: "SB",
      value: "Sì",
      textAlignment: "PKTextAlignmentCenter",
    },
    {
      key: "sec4",
      label: "Imbarco",
      value: "Anteriore",
      textAlignment: "PKTextAlignmentCenter",
    }
  );

  pass.auxiliaryFields.push(
    {
      key: "aux1",
      label: "Passeggero",
      value: "MR. WHO KNOWS",
      textAlignment: "PKTextAlignmentLeft",
    },
    {
      key: "aux2",
      label: "Posto",
      value: "1A*",
      textAlignment: "PKTextAlignmentCenter",
    }
  );

  pass.backFields.push(
    {
      key: "document number",
      label: "Numero documento:",
      value: "- -",
      textAlignment: "PKTextAlignmentLeft",
    },
    {
      key: "You're checked in, what next",
      label: "Hai effettuato il check-in, Quali sono le prospettive",
      value: "",
      textAlignment: "PKTextAlignmentLeft",
    },
    {
      key: "Check In",
      label: "1. check-in✓",
      value: "",
      textAlignment: "PKTextAlignmentLeft",
    },
    {
      key: "checkIn",
      label: "",
      value:
        "Le uscite d'imbarco chiudono 30 minuti prima della partenza, quindi sii puntuale. In questo aeroporto puoi utilizzare la corsia Fast Track ai varchi di sicurezza.",
      textAlignment: "PKTextAlignmentLeft",
    },
    {
      key: "2. Bags",
      label: "2. Bagaglio",
      value: "",
      textAlignment: "PKTextAlignmentLeft",
    },
    {
      key: "Require special assistance",
      label: "Assistenza speciale",
      value:
        "Se hai richiesto assistenza speciale, presentati a un membro del personale nell'area di Consegna bagagli almeno 90 minuti prima del volo.",
      textAlignment: "PKTextAlignmentLeft",
    },
    {
      key: "3. Departures",
      label: "3. Partenze",
      value: "",
      textAlignment: "PKTextAlignmentLeft",
    },
    {
      key: "photoId",
      label: "Un documento d’identità corredato di fotografia",
      value:
        "è obbligatorio su TUTTI i voli. Per un viaggio internazionale è necessario un passaporto valido o, dove consentita, una carta d’identità.",
      textAlignment: "PKTextAlignmentLeft",
    },
    {
      key: "yourSeat",
      label: "Il tuo posto:",
      value:
        "verifica il tuo numero di posto nella parte superiore. Durante l’imbarco utilizza le scale anteriori e posteriori: per le file 1-10 imbarcati dalla parte anteriore; per le file 11-31 imbarcati dalla parte posteriore. Colloca le borse di dimensioni ridotte sotto il sedile davanti a te.",
      textAlignment: "PKTextAlignmentLeft",
    },
    {
      key: "Pack safely",
      label: "Bagaglio sicuro",
      value:
        "Fai clic http://easyjet.com/it/articoli-pericolosi per maggiori informazioni sulle merci pericolose oppure visita il sito CAA http://www.caa.co.uk/default.aspx?catid=2200",
      textAlignment: "PKTextAlignmentLeft",
    },
    {
      key: "Thank you for travelling easyJet",
      label: "Grazie per aver viaggiato con easyJet",
      value: "",
      textAlignment: "PKTextAlignmentLeft",
    }
  );

  return new Response(pass.getAsBuffer(), {
    headers: {
      "Content-type": pass.mimeType,
      "Content-disposition": `attachment; filename=myPass.pkpass`,
    },
  });
}

@moishinetzer
Copy link
Author

For the record, this lib is amazing thanks for all your hard work

@alexandercerutti
Copy link
Owner

I'm not sure how remix works for cloudflare pages. Is the above code running on a CF Worker or something else?

@moishinetzer
Copy link
Author

It's working on a CF function (which is on workers) I have confirmed other "node:" modules work

@moishinetzer
Copy link
Author

moishinetzer commented May 16, 2024

I have found a solution (although quite convoluted)

  1. Replace all node APIs by prefixing with "node:"
  2. Remove usage of "os" and "fs" (I deleted the entire else branch where this is being used)
  3. Change the package.json to use "type": "module"
  4. Use tsup to build the package to esm
  5. Add import { Buffer } from 'node:buffer'; to the top of the 'do-not-zip' package.

Phew thats a lot!

Here are the patches you can use for patch-package that does the above:

do-not-zip+1.0.0.patch
passkit-generator+3.1.11.patch

(Update: still working on it - this works locally not deployed)

(Update x2: it totally works)

@alexandercerutti
Copy link
Owner

Those are a lot of changes. It feels weird Cloudflare requires modules to use node: prefix 🤔 Do you have any specific reference to this?

I understand the fs module could be incompatible as they do not let you access the storage like a firebase function does, but the os?

@alexandercerutti alexandercerutti mentioned this issue Jun 13, 2024
3 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants