Skip to content

Commit 5137c8a

Browse files
committed
feat(compas): add support for npx compas@latest init
This self inits according to the docs at https://compasjs.com/docs/getting-started.html#installation
1 parent 2b2f2bf commit 5137c8a

File tree

4 files changed

+238
-9
lines changed

4 files changed

+238
-9
lines changed

packages/compas/src/cli/bin.js

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,48 @@
11
#!/usr/bin/env node
22

3+
import { existsSync } from "node:fs";
34
import { isNil } from "@compas/stdlib";
45
import { configLoadEnvironment } from "../config.js";
5-
import { ciMode } from "../main/ci/index.js";
6-
import { developmentMode } from "../main/development/index.js";
7-
import { productionMode } from "../main/production/index.js";
86

9-
const env = await configLoadEnvironment("", !isNil(process.env.NODE_ENV));
7+
// Just execute some temporary command matching
8+
const args = process.argv.slice(2);
9+
const debug = args.includes("--debug");
1010

11-
if (env.isCI) {
12-
await ciMode(env);
13-
} else if (!env.isDevelopment) {
14-
await productionMode(env);
11+
if (debug) {
12+
args.splice(args.indexOf("--debug"), 1);
13+
}
14+
15+
if (args.length === 0) {
16+
if (!existsSync("./package.json")) {
17+
// eslint-disable-next-line no-console
18+
console.log(`Please run 'npx compas@latest init' to install Compas.`);
19+
} else {
20+
// TODO: check if we are in a project or someone forgot to run 'compas init'.
21+
22+
// TODO: debug
23+
24+
const env = await configLoadEnvironment("", !isNil(process.env.NODE_ENV));
25+
26+
if (env.isCI) {
27+
const { ciMode } = await import("../main/ci/index.js");
28+
await ciMode(env);
29+
} else if (!env.isDevelopment) {
30+
const { productionMode } = await import("../main/production/index.js");
31+
await productionMode(env);
32+
} else {
33+
const { developmentMode } = await import("../main/development/index.js");
34+
await developmentMode(env);
35+
}
36+
}
37+
} else if (args.length === 1) {
38+
if (args[0] === "init") {
39+
const { initCompas } = await import("../main/init/compas.js");
40+
await initCompas();
41+
}
1542
} else {
16-
await developmentMode(env);
43+
// eslint-disable-next-line no-console
44+
console.log(`Unsupported command. Available commands:
45+
46+
- compas
47+
- compas init`);
1748
}

packages/compas/src/config.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,42 @@ APP_NAME=${dirname}
8383
return env;
8484
}
8585

86+
/**
87+
* Load .env if exists, but other than that prepare for development mode.
88+
*
89+
* @returns {Promise<ConfigEnvironment>}
90+
*/
91+
export async function configLoadReadOnlyEnvironment() {
92+
// Load .env.local first, since existing values in `process.env` are not overwritten.
93+
dotenv.config({ path: path.resolve(process.cwd(), ".env.local") });
94+
dotenv.config();
95+
96+
refreshEnvironmentCache();
97+
98+
environment.COMPAS_LOG_PRINTER = "pretty";
99+
100+
const packageJson = JSON.parse(
101+
await readFile(
102+
pathJoin(dirnameForModule(import.meta), "../package.json"),
103+
"utf-8",
104+
),
105+
);
106+
107+
const env = {
108+
isCI: environment.CI === "true",
109+
isDevelopment: isNil(process.env.NODE_ENV) || !isProduction(),
110+
appName: environment.APP_NAME ?? process.cwd().split(path.sep).pop(),
111+
compasVersion: packageJson.version
112+
? `Compas ${packageJson.version}`
113+
: "Compas v0.0.0",
114+
nodeVersion: process.version,
115+
};
116+
117+
loggerDetermineDefaultDestination();
118+
119+
return env;
120+
}
121+
86122
/**
87123
* Try to load the config recursively from disk.
88124
*
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import { existsSync } from "node:fs";
2+
import { readFile, writeFile } from "node:fs/promises";
3+
import { exec, newLogger, spawn } from "@compas/stdlib";
4+
import { configLoadReadOnlyEnvironment } from "../../config.js";
5+
import { logger, loggerEnable } from "../../output/log.js";
6+
import { packageManagerDetermineInstallCommand } from "../../package-manager.js";
7+
8+
export async function initCompas() {
9+
const env = await configLoadReadOnlyEnvironment();
10+
11+
loggerEnable(
12+
newLogger({
13+
ctx: {
14+
type: env.appName,
15+
},
16+
}),
17+
);
18+
if (env.isCI) {
19+
logger.info({
20+
message: "Compas init is not supported in CI.",
21+
});
22+
return;
23+
}
24+
25+
if (!env.isDevelopment) {
26+
logger.info({
27+
message:
28+
"Compas init is not supported when NODE_ENV is explicitly set, but is not 'development'.",
29+
});
30+
return;
31+
}
32+
33+
if (existsSync("package.json")) {
34+
await initCompasInExistingProject(env);
35+
} else {
36+
await initCompasInNewProject(env);
37+
}
38+
}
39+
40+
async function initCompasInExistingProject(env) {
41+
const packageJson = JSON.parse(await readFile("package.json", "utf-8"));
42+
43+
let alreadyInstalled = false;
44+
const compasVersion = env.compasVersion.split("v").pop();
45+
46+
if (packageJson.dependencies?.compas) {
47+
alreadyInstalled = packageJson.dependencies.compas === compasVersion;
48+
packageJson.dependencies.compas = compasVersion;
49+
} else if (packageJson.devDependencies?.compas) {
50+
alreadyInstalled = packageJson.devDependencies.compas === compasVersion;
51+
packageJson.devDependencies.compas = compasVersion;
52+
} else {
53+
packageJson.dependencies ??= {};
54+
packageJson.dependencies.compas = compasVersion;
55+
}
56+
57+
if (!alreadyInstalled) {
58+
logger.info(
59+
`Patching package.json with ${env.compasVersion} and installing dependencies...`,
60+
);
61+
await writeFile(
62+
"package.json",
63+
`${JSON.stringify(packageJson, null, 2)}\n`,
64+
);
65+
const packageManagerCommand = packageManagerDetermineInstallCommand();
66+
await spawn(packageManagerCommand[0], packageManagerCommand.slice(1));
67+
68+
logger.info(`
69+
Ready to roll! Run 'npx compas' to start the Compas development environment.
70+
71+
Tip: See https://compasjs.com/docs/getting-started.html#development-setup on how to run 'compas' without the 'npx' prefix.
72+
`);
73+
} else {
74+
logger.info("Already up-to-date!");
75+
}
76+
}
77+
78+
async function initCompasInNewProject(env) {
79+
const compasVersion = env.compasVersion.split("v").pop();
80+
const packageJson = {
81+
name: env.appName,
82+
private: true,
83+
version: "0.0.1",
84+
type: "module",
85+
scripts: {},
86+
keywords: [],
87+
dependencies: {
88+
compas: compasVersion,
89+
},
90+
};
91+
92+
await writeFile("package.json", `${JSON.stringify(packageJson, null, 2)}\n`);
93+
94+
logger.info("Created a package.json. Installing with npm...");
95+
96+
const packageManagerCommand = packageManagerDetermineInstallCommand();
97+
await spawn(packageManagerCommand[0], packageManagerCommand.slice(1));
98+
99+
if (!existsSync(".gitignore")) {
100+
await writeFile(
101+
".gitignore",
102+
`# Compas
103+
.cache
104+
.env.local
105+
generated
106+
107+
# OS
108+
.DS_Store
109+
110+
# IDE
111+
.idea
112+
.vscode
113+
114+
# Dependencies
115+
node_modules
116+
117+
# Log files
118+
*-debug.log
119+
*-error.log
120+
121+
# Tests
122+
coverage
123+
`,
124+
);
125+
}
126+
127+
if ((await exec("git --version")).exitCode === 0 && !existsSync(".git")) {
128+
logger.info("Initializing a Git repository.");
129+
130+
await exec("git init");
131+
await exec("git checkout -b main");
132+
await exec("git add -A");
133+
await exec(`git commit -m "Initialized project with ${env.compasVersion}"`);
134+
}
135+
136+
logger.info(`
137+
Ready to roll! Run 'npx compas' to start the Compas development environment.
138+
139+
You can switch to a different supported package manager like Yarn or pnpm by removing the created package-lock.json and running the equivalent of 'npm install' with your favorite package manager.
140+
141+
Tip: See https://compasjs.com/docs/getting-started.html#development-setup on how to run 'compas' without the 'npx' prefix.
142+
`);
143+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { existsSync } from "node:fs";
2+
3+
/**
4+
* Determine package manager command to use for installing dependencies.
5+
*
6+
* @returns {string[]}
7+
*/
8+
export function packageManagerDetermineInstallCommand() {
9+
if (existsSync("package-lock.json")) {
10+
return ["npm", "install"];
11+
} else if (existsSync("yarn.lock")) {
12+
return ["yarn"];
13+
} else if (existsSync("pnpm-lock.yaml")) {
14+
return ["pnpm", "install"];
15+
}
16+
17+
// Default to NPM
18+
return ["npm", "install"];
19+
}

0 commit comments

Comments
 (0)