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

Fixes #7951: prevent remix.init errors when init script is an ES6 module, update docs to clarify init script requirements #8100

Closed
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
053cb6d
adjust import of remix.init script to also work with es6 modules, bar…
leothorp Nov 22, 2023
e87649c
add docs
leothorp Nov 22, 2023
58e90a9
wording adjustment
leothorp Nov 22, 2023
2ee1919
add test fixtures
leothorp Nov 22, 2023
052c076
add tests
leothorp Nov 22, 2023
9c1adca
fix spacing
leothorp Nov 22, 2023
c278a83
import
leothorp Nov 22, 2023
747e1d7
dynamic import
leothorp Nov 22, 2023
c9bcc87
naming
leothorp Nov 22, 2023
12e1434
fmt
leothorp Nov 22, 2023
70de3bb
Merge branch 'dev' into relax-remix-init-module-format-restrictions
leothorp Nov 22, 2023
bf8d7bc
Merge https://github.com/leothorp/remix into relax-remix-init-module-…
leothorp Nov 22, 2023
261d3ae
fix file path URL on Windows
leothorp Nov 22, 2023
76e0a68
Merge branch 'relax-remix-init-module-format-restrictions' of https:/…
leothorp Nov 22, 2023
ed1078e
Merge branch 'dev' into relax-remix-init-module-format-restrictions
leothorp Nov 22, 2023
9b7c69e
lint
leothorp Nov 22, 2023
3f80c6d
Merge branch 'relax-remix-init-module-format-restrictions' of https:/…
leothorp Nov 22, 2023
c52e2c5
second attempt windows filepath URL fix
leothorp Nov 22, 2023
593a5dd
second attempt windows filepath URL fix
leothorp Nov 22, 2023
14b7aaf
dynamic import on windows
leothorp Nov 22, 2023
7b9c2f3
lint
leothorp Nov 22, 2023
7c61603
typo
leothorp Nov 22, 2023
f099134
use execa
leothorp Nov 22, 2023
f67ea02
rethrow error
leothorp Nov 22, 2023
e8d00c7
pass logs
leothorp Nov 22, 2023
92bdc30
comp
leothorp Nov 22, 2023
1a3d619
wording
leothorp Nov 23, 2023
03646eb
rename
leothorp Nov 23, 2023
ad5c5b6
pass error check
leothorp Nov 23, 2023
e84281e
pass error check
leothorp Nov 23, 2023
f5baf4c
pass error check
leothorp Nov 23, 2023
8ae9579
up
leothorp Nov 23, 2023
1ed86b3
up
leothorp Nov 23, 2023
aabbcaa
up
leothorp Nov 23, 2023
b6accdd
up
leothorp Nov 23, 2023
c64d0d9
rename
leothorp Nov 23, 2023
af56384
correct fixture name
leothorp Nov 23, 2023
102490d
correct fixture name
leothorp Nov 23, 2023
29809a9
reword
leothorp Nov 23, 2023
e3d0608
use latest version
leothorp Nov 23, 2023
f90b64d
use latest version
leothorp Nov 23, 2023
f20d7dd
working dir
leothorp Nov 23, 2023
8225de4
Merge branch 'dev' into relax-remix-init-module-format-restrictions
leothorp Nov 23, 2023
21cbb77
experimenting
leothorp Nov 23, 2023
d15e2a8
rm unneeded test/fixture
leothorp Nov 23, 2023
0519f57
Merge branch 'relax-remix-init-module-format-restrictions' of https:/…
leothorp Nov 23, 2023
b2dec9c
forward --show-install-output value
leothorp Nov 23, 2023
2100928
fix import
leothorp Nov 23, 2023
766e07b
Merge branch 'dev' into relax-remix-init-module-format-restrictions
leothorp Nov 24, 2023
794afa9
Merge branch 'dev' into relax-remix-init-module-format-restrictions
leothorp Nov 26, 2023
299d203
Merge branch 'dev' into relax-remix-init-module-format-restrictions
leothorp Nov 29, 2023
66d945d
Merge branch 'dev' into relax-remix-init-module-format-restrictions
leothorp Nov 30, 2023
cbf288a
Merge branch 'dev' into relax-remix-init-module-format-restrictions
leothorp Dec 2, 2023
9d28ab6
Merge branch 'dev' into relax-remix-init-module-format-restrictions
leothorp Dec 5, 2023
035becf
revert create-remix changes
leothorp Dec 6, 2023
8e52f34
duplicate init function
leothorp Dec 6, 2023
cdfb761
remove comment
leothorp Dec 6, 2023
a2aac31
Merge branch 'dev' into relax-remix-init-module-format-restrictions
leothorp Dec 6, 2023
cea3080
Merge branch 'dev' into relax-remix-init-module-format-restrictions
leothorp Dec 6, 2023
8e448b6
Merge branch 'dev' into relax-remix-init-module-format-restrictions
leothorp Dec 6, 2023
73a92dd
Merge branch 'dev' into relax-remix-init-module-format-restrictions
leothorp Dec 6, 2023
e42c732
Merge branch 'dev' into relax-remix-init-module-format-restrictions
leothorp Dec 7, 2023
22cb64b
fmt
leothorp Dec 7, 2023
7b750d1
Merge branch 'dev' into relax-remix-init-module-format-restrictions
leothorp Dec 7, 2023
41bd56a
Merge branch 'dev' into relax-remix-init-module-format-restrictions
leothorp Dec 7, 2023
1b7dd69
Merge branch 'dev' into relax-remix-init-module-format-restrictions
leothorp Dec 9, 2023
4006f9b
Merge branch 'dev' into relax-remix-init-module-format-restrictions
leothorp Dec 11, 2023
cde35e4
Merge branch 'dev' into relax-remix-init-module-format-restrictions
leothorp Dec 12, 2023
3b788bc
Merge branch 'dev' into relax-remix-init-module-format-restrictions
leothorp Dec 14, 2023
4bf8653
Merge branch 'dev' into relax-remix-init-module-format-restrictions
leothorp Dec 18, 2023
86bcfbd
Merge branch 'dev' into relax-remix-init-module-format-restrictions
leothorp Dec 18, 2023
2a57eae
Merge branch 'dev' into relax-remix-init-module-format-restrictions
leothorp Jan 4, 2024
7e1b974
Merge branch 'dev' into relax-remix-init-module-format-restrictions
leothorp Jan 5, 2024
6e8c447
Merge branch 'dev' into relax-remix-init-module-format-restrictions
leothorp Jan 10, 2024
41d0187
Merge branch 'dev' into relax-remix-init-module-format-restrictions
leothorp Jan 10, 2024
873ec19
Merge branch 'dev' into relax-remix-init-module-format-restrictions
leothorp Jan 14, 2024
d5ade44
Merge branch 'dev' into relax-remix-init-module-format-restrictions
leothorp Jan 19, 2024
0cd4281
Merge branch 'dev' into relax-remix-init-module-format-restrictions
leothorp Jan 28, 2024
8872316
Merge branch 'dev' into relax-remix-init-module-format-restrictions
leothorp Jan 30, 2024
d37073d
Merge branch 'dev' into relax-remix-init-module-format-restrictions
leothorp Feb 9, 2024
27cb926
Merge branch 'dev' into relax-remix-init-module-format-restrictions
leothorp Feb 9, 2024
87534a4
Merge branch 'dev' into relax-remix-init-module-format-restrictions
leothorp Mar 21, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions contributors.yml
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,7 @@
- lensbart
- leo
- leon
- leothorp
- leovander
- levippaul
- LewisArdern
Expand Down
22 changes: 21 additions & 1 deletion docs/guides/templates.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,27 @@ This allows you to not have to regularly update your template to the latest vers

If the template has a `remix.init/index.js` file at the root then that file will be executed after the project has been generated and dependencies have been installed. This gives you a chance to do anything you'd like as part of the initialization of your template. For example, in the blues stack, the `app` property has to be globally unique, so we use the `remix.init/index.js` file to change it to the name of the directory that was created for the project + a couple random characters.

You could even use `remix.init/index.js` to ask further questions to the developer for additional configuration (using something like [inquirer][inquirer]). Sometimes, you'll need dependencies installed to do this, but those deps are only useful during initialization. In that case, you can also create a `remix.init/package.json` with dependencies and the Remix CLI will install those before running your script.
The `remix.init/index.js` file can contain code to be executed immediately:

```javascript
console.log('initializing project');
// further initialization code here
```

Or can contain a default export of a function that will be invoked. In that case, your init function will be passed the user's selected package manager and the root directory of their project:

```javascript
export default ({
packageManager, // "npm" | "pnpm" | "yarn" | "bun"
rootDirectory: // "path/to/project/root"

}) => {
console.log('initializing project');
// initialization code here
};
```

You could even use `remix.init/index.js` to ask further questions to the developer for additional configuration (using something like [inquirer][inquirer]). Sometimes, you'll need dependencies installed to do this, but those deps are only useful during initialization. In that case, you can also create a `remix.init/package.json` with dependencies and the Remix CLI will install those before running your script. Note: if you wish for your init script to be a commonjs module, and the top-level module of your project is defined as an ES6 module, `remix.init/package.json` will be required.

After the init script has been run, the `remix.init` folder gets deleted, so you don't need to worry about it cluttering up the finished codebase.

Expand Down
11 changes: 11 additions & 0 deletions packages/remix-dev/__tests__/fixtures/es6-remix-init/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "remix-init-as-es6-module-test",
"private": true,
"type": "module",
"scripts": {
"remix-init": "remix init"
},
"devDependencies": {
"@remix-run/dev": "latest"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import fs from "node:fs";
import path from "node:path";
export default ({rootDirectory}) => {
fs.writeFileSync(
path.join(rootDirectory, "es6-remix-init-test.txt"),
"added via es6 remix.init"
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "no-export-remix-init-test",
"private": true,
"main": "remix.int/index.js"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const fs = require("node:fs");

fs.writeFileSync(
"./no-export-remix-init.txt",
"added via remix.init with no export"
);
68 changes: 67 additions & 1 deletion packages/remix-dev/__tests__/init-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as os from "node:os";
import * as path from "node:path";
import fse from "fs-extra";
import stripAnsi from "strip-ansi";
import { execSync } from "node:child_process";

import { run } from "../cli/run";

Expand Down Expand Up @@ -31,6 +32,31 @@ jest.mock("child_process", () => {
};
});

// Copy over the current build of @remix-run/dev and execute remix init from the node_modules dir there.
// Certain bugs only manifest when using this approach.
const execRemixInitInProject = (projectDir: string) => {
Copy link
Contributor Author

@leothorp leothorp Nov 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This stuff with copying over the newly-built version of @remix-run/dev and running it as a node module in the temp project is clunky, but was the only way I could reproduce the failing behavior of an es6 module init script throwing an error in the unit tests. With run(["init"]) the test always succeeds with the old code, even though remix init errors in actual use.

It seems like some aspect of the run(["init"]) calls used by other tests here doesn't fully replicate the real-world behavior of running remix init as a project dependency, so this was the only workaround I found initially .

execSync(`npm install`, {
stdio: "pipe",
});

// overwrite installed @remix-run/dev with the one we just built
fse.copySync(
path.join(
__dirname,
"../../../",
"build",
"node_modules",
"@remix-run/dev"
),
path.join(projectDir, "node_modules", "@remix-run/dev")
);

fse.chmodSync(path.join(projectDir, "node_modules", ".bin/remix"), 0o755);
execSync(`npm run remix-init`, {
stdio: "pipe",
});
};

const TEMP_DIR = path.join(
fse.realpathSync(os.tmpdir()),
`remix-tests-${Math.random().toString(32).slice(2)}`
Expand All @@ -50,6 +76,10 @@ let originalLog = console.log;
let originalWarn = console.warn;
let originalError = console.error;

const convertLogToOutputLine = (messageString: string) => {
return "\n" + stripAnsi(messageString).replace(TEMP_DIR, "<TEMP_DIR>");
};

beforeEach(async () => {
output = "";
function hijackLog(message: unknown = "", ...rest: Array<unknown>) {
Expand All @@ -58,14 +88,15 @@ beforeEach(async () => {
if (typeof message === "string" && message.startsWith("debug:")) {
return originalLog(message, ...rest);
}

let messageString =
typeof message === "string" ? message : JSON.stringify(message, null, 2);
if (rest[0]) {
throw new Error(
"Our tests are not set up to handle multiple arguments to console.log."
);
}
output += "\n" + stripAnsi(messageString).replace(TEMP_DIR, "<TEMP_DIR>");
output += convertLogToOutputLine(messageString);
}
console.log = hijackLog;
console.warn = hijackLog;
Expand Down Expand Up @@ -130,6 +161,41 @@ describe("the init command", () => {
expect(fse.existsSync(path.join(projectDir, "remix.init"))).toBeTruthy();
});

it("succeeds for a remix.init script that doesn't default export a function", async () => {
let projectDir = await getProjectDir("remix-init-manual-no-export");

fse.copySync(
path.join(__dirname, "fixtures", "no-export-remix-init"),
projectDir
);
process.chdir(projectDir);
await run(["init"]);

expect(
fse.existsSync(path.join(projectDir, "no-export-remix-init.txt"))
).toBeTruthy();

// expect(output).toBe(convertLogToOutputLine("exportless commonjs remix init with ES6 parent module succeeded"));
expect(fse.existsSync(path.join(projectDir, "remix.init"))).toBeFalsy();
});

it("succeeds for remix.init defined as an ES6 module", async () => {
let projectDir = await getProjectDir("remix-init-manual-es6");

fse.copySync(
path.join(__dirname, "fixtures", "es6-remix-init"),
projectDir
);
process.chdir(projectDir);
execRemixInitInProject(projectDir);

expect(output).toBe("");
expect(
fse.existsSync(path.join(projectDir, "es6-remix-init-test.txt"))
).toBeTruthy();
expect(fse.existsSync(path.join(projectDir, "remix.init"))).toBeFalsy();
});

it("throws an error when invalid remix.init script when manually ran", async () => {
let projectDir = await getProjectDir("invalid-remix-init-manual");

Expand Down
7 changes: 5 additions & 2 deletions packages/remix-dev/cli/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,15 @@ export async function init(
});
}

let initFn = require(initScript);
let initFn = await import(initScript);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Covered by the "succeeds for remix.init defined as an ES6 module" test.


if (typeof initFn !== "function" && initFn.default) {
initFn = initFn.default;
}
try {
await initFn({ packageManager, rootDirectory: projectDir });
if (typeof initFn === "function") {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Covered by the "succeeds for a remix.init script that doesn't default export a function" test.

await initFn({ packageManager, rootDirectory: projectDir });
}

if (deleteScript) {
await fse.remove(initScriptDir);
Expand Down
2 changes: 1 addition & 1 deletion packages/remix-dev/cli/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ export async function run(argv: string[] = process.argv.slice(2)) {
}

let command = input[0];

// Note: Keep each case in this switch statement small.
switch (command) {
case "init":
Expand Down