Skip to content
Merged

Dev #87

Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions .changeset/fix-starter-gitignore-packaging.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@runablejs/cli": patch
---

Ensure every generated starter includes its framework-specific `.gitignore` file.
5 changes: 5 additions & 0 deletions .changeset/inherit-module-aliases.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"runable": patch
---

Inherit aliases declared by modules in application Vite and TypeScript resolution.
6 changes: 5 additions & 1 deletion packages/cli/src/commands/create/starter.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { cp, mkdir, readFile, stat, writeFile } from "node:fs/promises";
import { cp, mkdir, readFile, rename, stat, writeFile } from "node:fs/promises";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";

Expand Down Expand Up @@ -75,6 +75,10 @@ export async function copyStarterTemplate(
await mkdir(targetDir, { recursive: true });
await cp(sharedTemplateDir, targetDir, { recursive: true, force: true });
await cp(templateDir, targetDir, { recursive: true, force: true });
await rename(
resolve(targetDir, "gitignore.template"),
resolve(targetDir, ".gitignore"),
);

const packageJsonPath = resolve(targetDir, "package.json");
const packageJson = JSON.parse(await readFile(packageJsonPath, "utf8"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,4 @@ yarn-error.log
.DS_Store

.app
.output
.output
19 changes: 18 additions & 1 deletion packages/runable/src/config/load.ts
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,21 @@ export interface ConfigGraph {
all: ResolvedConfig[];
}

/**
* Combines aliases from the resolved config graph. Dependencies are applied
* first, then their parents, and the main application last, so the closest
* consumer wins when two configs declare the same alias.
*/
function mergeConfigAliases(configs: ResolvedConfig[]) {
const aliases: ResolvedConfig["alias"] = {};

for (const config of [...configs].reverse()) {
Object.assign(aliases, config.alias);
}

return aliases;
}

/**
* Resolves the full config graph for a project — the main config, every
* module it transitively depends on, their options, and module `setup()`
Expand Down Expand Up @@ -585,10 +600,12 @@ export async function resolveConfigGraph(

resolved = await runConfigExtensions(resolved, pendingSetups);
const main = resolved.__main!;
const all = Object.values(resolved).sort((a, b) => a._index - b._index);

main.alias = mergeConfigAliases(all);

await runSetups(pendingSetups, main);

const all = Object.values(resolved).sort((a, b) => a._index - b._index);
return { main, all };
}

Expand Down
2 changes: 1 addition & 1 deletion packages/runable/src/utils/tsconfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export function writeTsConfig() {
Object.entries(alias ?? {}).forEach(([key, value]) => {
if (key === "#build") return;

tsconfig.app.addAlias(key, normalizeDir(relative(process.cwd(), value)));
tsconfig.app.addAlias(key, normalizeDir(relative(output, value)));
});

tsconfig.app.addAlias("#build", "./");
Expand Down
8 changes: 8 additions & 0 deletions tests/integration/agents-md.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ describe("AGENTS.md is shipped to newly scaffolded Runable projects", () => {
path.join(starterTemplateDir, "package.json"),
JSON.stringify({ name: "fixture-starter", version: "1.0.0" }),
);
writeFileSync(
path.join(starterTemplateDir, "gitignore.template"),
"node_modules\n.app\n.output\n",
);

const { copyStarterTemplate } = await import(
"../../packages/cli/dist/commands/create/starter.js"
Expand All @@ -95,6 +99,10 @@ describe("AGENTS.md is shipped to newly scaffolded Runable projects", () => {
// The starter template itself is still there too — copyAgentsFile
// must not have clobbered anything from copyStarterTemplate.
expect(existsSync(path.join(projectDir, "package.json"))).toBe(true);
expect(existsSync(path.join(projectDir, ".gitignore"))).toBe(true);
expect(existsSync(path.join(projectDir, "gitignore.template"))).toBe(
false,
);
} finally {
rmSync(starterTemplateDir, { recursive: true, force: true });
cleanupFixtureDir(projectDir);
Expand Down
8 changes: 7 additions & 1 deletion tests/regressions/cli-starters.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@ describe("CLI starter templates", () => {
it(`copies a complete ${framework} starter`, async () => {
expect(
existsSync(
join(process.cwd(), "packages/cli/starters", framework, ".gitignore"),
join(
process.cwd(),
"packages/cli/starters",
framework,
"gitignore.template",
),
),
).toBe(true);

Expand All @@ -32,6 +37,7 @@ describe("CLI starter templates", () => {

expect(existsSync(join(target, "package.json"))).toBe(true);
expect(existsSync(join(target, ".gitignore"))).toBe(true);
expect(existsSync(join(target, "gitignore.template"))).toBe(false);
expect(existsSync(join(target, "runable.config.ts"))).toBe(true);
expect(existsSync(join(target, "app/app.vue"))).toBe(true);
expect(existsSync(join(target, "app/pages/index.vue"))).toBe(true);
Expand Down
75 changes: 75 additions & 0 deletions tests/regressions/module-aliases.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { readFileSync } from "node:fs";
import path from "node:path";
import { afterEach, describe, expect, it, vi } from "vitest";

import {
cleanupFixtureDir,
createFixtureDir,
linkWorkspacePackage,
writeFixtureFile,
} from "../fixtures.js";

const originalCwd = process.cwd();

afterEach(() => {
process.chdir(originalCwd);
});

describe("module aliases", () => {
it("adds module aliases to the application with consumer precedence", async () => {
const directory = createFixtureDir("module-aliases-");

try {
linkWorkspacePackage(directory, "runable", "packages/runable");
writeFixtureFile(
directory,
"runable.config.ts",
`import path from "node:path";
import { defineConfig } from "runable";

export default defineConfig({
modules: ["./module"],
alias: { "@shared": path.join(import.meta.dirname, "app/shared") },
});
`,
);
writeFixtureFile(
directory,
"module/runable.config.ts",
`import path from "node:path";
import { defineModule } from "runable";

export default defineModule({
alias: {
"@module": path.join(import.meta.dirname, "app"),
"@shared": path.join(import.meta.dirname, "app/shared"),
},
});
`,
);

process.chdir(directory);
vi.resetModules();
const { loadConfig, useConfig, writeTsConfig } = await import("runable");
await loadConfig();
writeTsConfig();

const config = useConfig();
expect(config.alias["@module"]).toBe(
path.join(directory, "module/app"),
);
expect(config.alias["@shared"]).toBe(path.join(directory, "app/shared"));
expect(config.alias["#build"]).toBe(path.join(directory, ".app"));

const generated = JSON.parse(
readFileSync(path.join(directory, ".app/tsconfig.app.json"), "utf8"),
);
expect(generated.compilerOptions.paths).toMatchObject({
"@module": ["../module/app"],
"@shared": ["../app/shared"],
});
} finally {
cleanupFixtureDir(directory);
}
});
});
Loading
Loading