sveltejs / kit

web development, streamlined
https://kit.svelte.dev
MIT License
18.48k stars 1.89k forks source link

Unable to use SvelteKit if path from root contains special characters #9833

Closed WillsterJohnson closed 1 year ago

WillsterJohnson commented 1 year ago

Describe the bug

For organisation and clarity, I've recently taken to prefixing directory names as follows;

I accept that it's a little weird, but @#$ are all valid characters for a directory name. To put it in perspective for those of you who only use alphanumeric directory names, read the rest of this issue as if the cause of the problem was using the letter Q in a directory name.

I've been migrating from my previous messy folder structure to this structure over the last few weeks, and today is the first time I've ran a kit project from the new structure.

Unfortunately, it appears that the presence of these characters at any point in the path causes an issue when running a Kit project in dev mode.

Reproduction

Unfortunately a repo reproduction won't do anything as this issue is directly caused by things outside of the project itself.

Run the following command chain (intended for bash - don't run this on windows);

mkdir \#test && cd \#test && npm create svelte@latest && npm install && npm run dev

Or do the following steps however you like;

  1. Create a directory which contains one of the following characters in it's name; @#$
  2. Initialise a SvelteKit project inside the new directory
  3. Install dependencies
  4. Run the dev command

Logs

.../TEST $: ll
total 0
.../TEST $: node -v
v18.13.0
.../TEST $: npm -v
8.19.3
.../TEST $: npm create svelte@latest && npm install && git init && git add -A && git commit -m "Initial commit" && npm run dev

create-svelte version 4.1.1

┌  Welcome to SvelteKit!
│
◇  Where should we create your project?
│    (hit Enter to use current directory)
│
◇  Which Svelte app template?
│  Skeleton project
│
◇  Add type checking with TypeScript?
│  Yes, using TypeScript syntax
│
◇  Select additional options (use arrow keys/space bar)
│  Add Prettier for code formatting
│
└  Your project is ready!

✔ Typescript
  Inside Svelte components, use <script lang="ts">

✔ Prettier
  https://prettier.io/docs/en/options.html
  https://github.com/sveltejs/prettier-plugin-svelte#options

Install community-maintained integrations:
  https://github.com/svelte-add/svelte-add

Next steps:
  1: npm install (or pnpm install, etc)
  2: git init && git add -A && git commit -m "Initial commit" (optional)
  3: npm run dev -- --open

To close the dev server, hit Ctrl-C

Stuck? Visit us at https://svelte.dev/chat

added 100 packages, and audited 101 packages in 2s

11 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities
Initialised empty Git repository in /media/will/NVMe250/TEST/.git/
[main (root-commit) a3ac38b] Initial commit
 14 files changed, 2765 insertions(+)
 create mode 100644 .gitignore
 create mode 100644 .npmrc
 create mode 100644 .prettierignore
 create mode 100644 .prettierrc
 create mode 100644 README.md
 create mode 100644 package-lock.json
 create mode 100644 package.json
 create mode 100644 src/app.d.ts
 create mode 100644 src/app.html
 create mode 100644 src/routes/+page.svelte
 create mode 100644 static/favicon.png
 create mode 100644 svelte.config.js
 create mode 100644 tsconfig.json
 create mode 100644 vite.config.ts

> test@0.0.1 dev
> vite dev

Forced re-optimization of dependencies

  VITE v4.3.4  ready in 660 ms

  ➜  Local:   http://localhost:5173/
  ➜  Network: use --host to expose
  ➜  press h to show help
X .../TEST $: cd ..
.../VAULT $: mv ./TEST ./#TEST
renamed './TEST' -> './#TEST'
.../VAULT $: cd \#TEST/
/home/will/Desktop/VAULT/#TEST
.../#TEST $: npm run dev

> test@0.0.1 dev
> vite dev

node:internal/process/promises:288
            triggerUncaughtException(err, true /* fromPromise */);
            ^

[Error: EISDIR: illegal operation on a directory, read] {
  errno: -21,
  code: 'EISDIR',
  syscall: 'read'
}

Node.js v18.13.0

System Info

System:
    OS: Linux 6.2 Pop!_OS 22.04 LTS
    CPU: (8) x64 Intel(R) Core(TM) i5-10210U CPU @ 1.60GHz
    Memory: 7.77 GB / 15.34 GB
    Container: Yes
    Shell: 5.1.16 - /bin/bash
  Binaries:
    Node: 18.13.0 - ~/.nvm/versions/node/v18.13.0/bin/node
    npm: 8.19.3 - ~/.nvm/versions/node/v18.13.0/bin/npm
  Browsers:
    Chrome: 112.0.5615.165
    Firefox: 112.0.1
  npmPackages:
    @sveltejs/adapter-auto: ^2.0.0 => 2.0.1 
    @sveltejs/kit: ^1.5.0 => 1.15.10 
    svelte: ^3.54.0 => 3.58.0 
    vite: ^4.3.0 => 4.3.4

Severity

serious, but I can work around it

Additional Information

This appears to persist regardless of;

It also only effects the dev command; preview, build, check, etc all work correctly, as does running arbitrary JS with node.

I also went into vite's bin and added dozens of console.log in there and in various imported files. They all logged and then the EISDIR was thrown. This may be a Vite issue, or potentially an issue with one of Vite's or SvelteKit's dependencies which manage file operations, but I'm not familiar enough with the internals of either project to properly debug this and find the correct source of the problem.

WillsterJohnson commented 1 year ago

I've been able to track this down to the saveSvelteMetadata function in the SvelteKit vite plugin, with the following change;

try {
+   console.log( svelteMetadataPath )
+   console.log((await fs5.stat(svelteMetadataPath)))
+   console.log({isdir: (await fs5.stat(svelteMetadataPath)).isDirectory()})
    existingSvelteMetadata = await fs5.readFile(svelteMetadataPath, "utf8" );
+   console.log("Check if the above line causes issue")
  } catch {
  }

This is the output;

/media/will/NVMe250/$GITHUB/@WILLSTERJOHNSON/#ARGON/node_modules/.vite/_svelte_metadata.json
Stats {
  dev: 66309,
  mode: 33204,
  nlink: 1,
  uid: 1000,
  gid: 1000,
  rdev: 0,
  blksize: 4096,
  ino: 9049681,
  size: 3006,
  blocks: 8,
  atimeMs: 1683125873260.6162,
  mtimeMs: 1683125857596.494,
  ctimeMs: 1683125857596.494,
  birthtimeMs: 1683117266112.142,
  atime: 2023-05-03T14:57:53.261Z,
  mtime: 2023-05-03T14:57:37.596Z,
  ctime: 2023-05-03T14:57:37.596Z,
  birthtime: 2023-05-03T12:34:26.112Z
}
{ isdir: false }
node:internal/process/promises:289
            triggerUncaughtException(err, true /* fromPromise */);
            ^

[Error: EISDIR: illegal operation on a directory, read] {
  errno: -21,
  code: 'EISDIR',
  syscall: 'read'
}

This is as far as I think I'm able to go, I tried casting it to a URL but that didn't change the results. However it looks like the problem is coming from fs5.readFile which is from Node... I haven't been able to find any workaround with relative paths either.

I've also checked that the path being pointed to exists and is a file; it does and it is;

{"compilerOptions":{"css":"external","dev":true,"hydratable":true},"configFile":false,"extensions":[".svelte"],"preprocess":[{"script":"async script({ attributes, content, filename = \"\" }) {\n      const lang = attributes.lang;\n      if (!supportedScriptLangs.includes(lang))\n        return;\n      const { code, map } = await transformWithEsbuild(content, filename, {\n        loader: lang,\n        target: \"esnext\",\n        tsconfigRaw: {\n          compilerOptions: {\n            // svelte typescript needs this flag to work with type imports\n            importsNotUsedAsValues: \"preserve\",\n            preserveValueImports: true\n          }\n        }\n      });\n      mapToRelative(map, filename);\n      return {\n        code,\n        map\n      };\n    }","style":"async ({ attributes, content, filename = \"\" }) => {\n    const lang = attributes.lang;\n    if (!supportedStyleLangs.includes(lang))\n      return;\n    if (!transform) {\n      let resolvedConfig;\n      if (style.__resolvedConfig) {\n        resolvedConfig = style.__resolvedConfig;\n      } else if (isResolvedConfig(config)) {\n        resolvedConfig = config;\n      } else {\n        resolvedConfig = await resolveConfig(\n          config,\n          process.env.NODE_ENV === \"production\" ? \"build\" : \"serve\"\n        );\n      }\n      transform = getCssTransformFn(resolvedConfig);\n    }\n    const suffix = `${lang_sep}${lang}`;\n    const moduleId = `${filename}${suffix}`;\n    const { code, map, deps } = await transform(content, moduleId);\n    removeLangSuffix(map, suffix);\n    mapToRelative(map, filename);\n    const dependencies = deps ? Array.from(deps).filter((d) => !d.endsWith(suffix)) : void 0;\n    return {\n      code,\n      map: map ?? void 0,\n      dependencies\n    };\n  }"},{"script":"({ content, filename }) => {\n\t\tif (!filename) return;\n\n\t\tconst basename = path.basename(filename);\n\t\tif (basename.startsWith('+page.') || basename.startsWith('+layout.')) {\n\t\t\tconst match = content.match(options_regex);\n\t\t\tif (match) {\n\t\t\t\tconst fixed = basename.replace('.svelte', '(.server).js/ts');\n\n\t\t\t\tconst message =\n\t\t\t\t\t`\\n${colors.bold().red(path.relative('.', filename))}\\n` +\n\t\t\t\t\t`\\`${match[1]}\\` will be ignored — move it to ${fixed} instead. See https://kit.svelte.dev/docs/page-options for more information.`;\n\n\t\t\t\tif (!warned.has(message)) {\n\t\t\t\t\tconsole.log(message);\n\t\t\t\t\twarned.add(message);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}","markup":"({ content, filename }) => {\n\t\tif (!filename) return;\n\n\t\tconst basename = path.basename(filename);\n\t\tif (basename.startsWith('+layout.') && !content.includes('<slot')) {\n\t\t\tconst message =\n\t\t\t\t`\\n${colors.bold().red(path.relative('.', filename))}\\n` +\n\t\t\t\t`\\`<slot />\\` missing — inner content will not be rendered`;\n\n\t\t\tif (!warned.has(message)) {\n\t\t\t\tconsole.log(message);\n\t\t\t\twarned.add(message);\n\t\t\t}\n\t\t}\n\t}"}]}
WillsterJohnson commented 1 year ago

Interestingly though, this issue doesn't happen when I try the following minimum reproduction for fs.promises.readFile

const fs = require("fs");
const path = require("path");

async function test() {
  const paths = [
    path.resolve(__dirname, "./$dollar/text.txt"),
    path.resolve(__dirname, "./$dollar/@at/text.txt"),
    path.resolve(__dirname, "./$dollar/@at/#hash/text.txt"),
    path.resolve(__dirname, "./@at/text.txt"),
    path.resolve(__dirname, "./@at/#hash/text.txt"),
    path.resolve(__dirname, "./#hash/text.txt"),
    path.resolve(__dirname, "./text.txt"),
  ];
  for (const p of paths) {
    console.log(p, ">>", await fs.promises.readFile(p, "utf8"));
  }
}

test();
$: node test.js
/media/will/NVMe250/TEST/$dollar/text.txt >> text
/media/will/NVMe250/TEST/$dollar/@at/text.txt >> text
/media/will/NVMe250/TEST/$dollar/@at/#hash/text.txt >> text
/media/will/NVMe250/TEST/@at/text.txt >> text
/media/will/NVMe250/TEST/@at/#hash/text.txt >> text
/media/will/NVMe250/TEST/#hash/text.txt >> text
/media/will/NVMe250/TEST/text.txt >> text

Screenshot from 2023-05-03 16-34-40

WillsterJohnson commented 1 year ago

It turns out that only the # causes this, @ and $ are fine. The issue will show up regardless of where in the file path to the project root the # is.

For now I'll find a different symbol to use where I'm using #, but it would be nice if this could be resolved

bluwy commented 1 year ago

If it's only the #, it could be a Vite issue. Curious, but do you get a different behaviour between Vite 4.2 and 4.3? We changed a bit of how it handles #. The reason why # can be problematic is because the file paths could appear as URLs as-is, and a # is a URL hash which makes handling it tricky.

WillsterJohnson commented 1 year ago

I tried downgrading vite to @4.2;

$: pnpm vite -v
vite/4.2.2 linux-x64 node-v19.8.1
$: pnpm vite dev

node:internal/process/promises:289
            triggerUncaughtException(err, true /* fromPromise */);
            ^

[Error: EISDIR: illegal operation on a directory, read] {
  errno: -21,
  code: 'EISDIR',
  syscall: 'read'
}

Node.js v19.8.1

Same issue unfortunately, it's also present in 4.0 and 4.1, and anything pre-v4 Kit isn't compatible with (ie, instant crash).

And yes, it turns out after messing around with lots of silly dir names it's only # which causes this. It would be nice if it didn't, but I can work around it for now by using a different character.

I actually discovered this weird bug when trying to work on a Kit+Tauri project which aims to make managing local dev files easier, which I find quite ironic.

Rich-Harris commented 1 year ago

Is it possible to reproduce this in a vanilla Vite (no SvelteKit) project? It might be worth opening an issue over there, as I suspect there's nothing we can do about it on our end

WillsterJohnson commented 1 year ago

yup, lots of weirdness going on in a vanilla vite project. I'll bring the issue up over there.