diff --git a/.changeset/create-module-playground.md b/.changeset/create-module-playground.md new file mode 100644 index 0000000..d9664bc --- /dev/null +++ b/.changeset/create-module-playground.md @@ -0,0 +1,5 @@ +--- +"@runablejs/cli": minor +--- + +Create a private workspace Runable playground that loads the module root when scaffolding a module. diff --git a/.changeset/include-parent-local-modules.md b/.changeset/include-parent-local-modules.md new file mode 100644 index 0000000..2c7ac7c --- /dev/null +++ b/.changeset/include-parent-local-modules.md @@ -0,0 +1,5 @@ +--- +"runable": patch +--- + +Include local modules located outside the application directory in the generated TypeScript configuration, and only inherit aliases from modules referenced by local paths. diff --git a/.changeset/use-starter-tsconfig-references.md b/.changeset/use-starter-tsconfig-references.md new file mode 100644 index 0000000..2f8df53 --- /dev/null +++ b/.changeset/use-starter-tsconfig-references.md @@ -0,0 +1,5 @@ +--- +"@runablejs/cli": patch +--- + +Use root TypeScript project references in generated starters and keep server configuration in `tsconfig.node.json`. diff --git a/packages/cli/src/commands/create/module.ts b/packages/cli/src/commands/create/module.ts index 3719626..0291518 100644 --- a/packages/cli/src/commands/create/module.ts +++ b/packages/cli/src/commands/create/module.ts @@ -1,5 +1,6 @@ -import { mkdir } from "node:fs/promises"; -import { resolve } from "node:path"; +import { cp, mkdir, writeFile } from "node:fs/promises"; +import { dirname, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; import * as p from "@clack/prompts"; import { consola } from "consola"; @@ -7,9 +8,11 @@ import { consola } from "consola"; import { exitOnCancel, type BaseProjectAnswers, - copyAppTemplate, handleSharedAnswers, afterAnswer, + copyServerEntry, + getCliPackageVersion, + writeRunableConfig, } from "./shared.js"; /** Answers collected for the "create a Runable module" flow: the shared answers plus the module's own identity (name and `configKey`). */ @@ -56,6 +59,78 @@ function printSummary(answers: ModuleProjectAnswers): void { consola.info(` installDeps: ${answers.installDeps ? "yes" : "no"}`); } +/** Creates the local Runable application used to develop and test a module. */ +export async function createModulePlayground( + moduleDir: string, + options: { + moduleName: string; + framework: string; + createServerEntry: boolean; + }, +) { + const __dirname = dirname(fileURLToPath(import.meta.url)); + const sharedStarterDir = resolve(__dirname, "../../../starters/_shared"); + const playgroundDir = resolve(moduleDir, "playground"); + + await mkdir(playgroundDir, { recursive: true }); + await cp(resolve(sharedStarterDir, "app"), resolve(playgroundDir, "app"), { + recursive: true, + force: true, + }); + await cp( + resolve(sharedStarterDir, "tsconfig.json"), + resolve(playgroundDir, "tsconfig.json"), + ); + await cp( + resolve(sharedStarterDir, "tsconfig.node.json"), + resolve(playgroundDir, "tsconfig.node.json"), + ); + await writeRunableConfig({ modules: [".."] }, { cwd: playgroundDir }); + await copyServerEntry( + options.createServerEntry, + options.framework, + playgroundDir, + ); + + const version = await getCliPackageVersion(); + const packageJson = { + name: `${options.moduleName.replace(/^@/, "").replace("/", "-")}-playground`, + private: true, + type: "module", + scripts: { + ...(options.createServerEntry + ? { dev: "runable prepare && tsx watch server.ts" } + : {}), + prepare: "runable prepare", + build: "runable build", + typecheck: "tsc --noEmit", + }, + dependencies: { + [options.moduleName]: "*", + ...(options.framework === "express" && options.createServerEntry + ? { express: "^5.2.1" } + : {}), + runable: version, + vue: "^3.5.0", + "vue-router": "^5.2.0", + }, + devDependencies: { + "@runablejs/cli": version, + ...(options.framework === "express" && options.createServerEntry + ? { "@types/express": "^5.0.6" } + : {}), + "@types/node": "^24.13.3", + tsx: "^4.23.12", + typescript: "^6.0.3", + }, + }; + + await writeFile( + resolve(playgroundDir, "package.json"), + `${JSON.stringify(packageJson, null, 2)}\n`, + ); +} + /** * Runs the "create a Runable module" flow: asks for the module's identity, * scaffolds a new directory named after it, copies the app template into @@ -77,16 +152,19 @@ export async function handleModuleProject() { const moduleDir = resolve(process.cwd(), moduleName); await mkdir(moduleDir, { recursive: true }); - const appDirResolved = resolve(moduleDir, answers.appDir); - await copyAppTemplate(appDirResolved); - // Record the config key alongside the other shared config values so it // ends up in the module's generated runable.config. Object.assign(answers._config, { configKey }); + await createModulePlayground(moduleDir, { moduleName, ...answers }); + // `isModule: true` tells `afterAnswer` to scaffold module-specific output // (e.g. `defineModule` instead of `defineConfig`) rather than a regular project. - await afterAnswer(moduleDir, answers, { moduleName, isModule: true }); + await afterAnswer( + moduleDir, + { ...answers, createServerEntry: false }, + { moduleName, isModule: true }, + ); // printSummary(answers); diff --git a/packages/cli/src/commands/create/shared.ts b/packages/cli/src/commands/create/shared.ts index 25441a8..1b97045 100644 --- a/packages/cli/src/commands/create/shared.ts +++ b/packages/cli/src/commands/create/shared.ts @@ -474,9 +474,12 @@ export async function createPackageJson( module: "./dist/index.js", types: "./dist/index.d.ts", files: ["dist"], + workspaces: ["playground"], scripts: { build: "runable build", "app:prepare": "runable prepare", + "playground:prepare": "cd playground && runable prepare", + "playground:build": "cd playground && runable build", }, devDependencies: { "@runablejs/cli": version, @@ -513,6 +516,20 @@ export async function configurePnpmBuilds( const document = parseDocument(source); if (document.errors.length > 0) throw document.errors[0]; + try { + const packageJson = JSON.parse( + await readFile(resolve(cwd, "package.json"), "utf8"), + ); + if ( + document.get("packages") === undefined && + Array.isArray(packageJson.workspaces) + ) { + document.set("packages", packageJson.workspaces); + } + } catch { + // A package.json is optional for the existing-project flow. + } + const esbuildPermission = document.getIn(["allowBuilds", "esbuild"]); if (esbuildPermission === undefined || esbuildPermission === null) { document.setIn(["allowBuilds", "esbuild"], true); diff --git a/packages/cli/starters/_shared/app/tsconfig.json b/packages/cli/starters/_shared/app/tsconfig.json deleted file mode 100644 index 6e9018d..0000000 --- a/packages/cli/starters/_shared/app/tsconfig.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "../.app/tsconfig.app.json", - "compilerOptions": { - "paths": {} - } -} - diff --git a/packages/cli/starters/_shared/tsconfig.json b/packages/cli/starters/_shared/tsconfig.json index b5351cf..92b0eee 100644 --- a/packages/cli/starters/_shared/tsconfig.json +++ b/packages/cli/starters/_shared/tsconfig.json @@ -1,14 +1,7 @@ { - "compilerOptions": { - "target": "ES2023", - "module": "NodeNext", - "moduleResolution": "NodeNext", - "strict": true, - "skipLibCheck": true, - "noEmit": true, - "types": ["node"] - }, - "include": ["**/*.ts", "**/*.vue"], - "exclude": ["node_modules", ".app", ".output"] + "files": [], + "references": [ + { "path": ".app/tsconfig.app.json" }, + { "path": "tsconfig.node.json" } + ] } - diff --git a/packages/cli/starters/_shared/tsconfig.node.json b/packages/cli/starters/_shared/tsconfig.node.json new file mode 100644 index 0000000..d7cff92 --- /dev/null +++ b/packages/cli/starters/_shared/tsconfig.node.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "ES2023", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "strict": true, + "skipLibCheck": true, + "noEmit": true, + "types": ["node"] + }, + "include": ["**/*.ts", "**/*.vue"], + "exclude": ["node_modules", ".app", ".output", "app"] +} diff --git a/packages/cli/starters/adonisjs/tsconfig.json b/packages/cli/starters/adonisjs/tsconfig.node.json similarity index 82% rename from packages/cli/starters/adonisjs/tsconfig.json rename to packages/cli/starters/adonisjs/tsconfig.node.json index 86b69e8..c4b1786 100644 --- a/packages/cli/starters/adonisjs/tsconfig.json +++ b/packages/cli/starters/adonisjs/tsconfig.node.json @@ -3,5 +3,6 @@ "compilerOptions": { "rootDir": "./", "outDir": "./build" - } + }, + "exclude": ["app"] } diff --git a/packages/cli/starters/nestjs/tsconfig.json b/packages/cli/starters/nestjs/tsconfig.node.json similarity index 77% rename from packages/cli/starters/nestjs/tsconfig.json rename to packages/cli/starters/nestjs/tsconfig.node.json index 9883918..dc6b988 100644 --- a/packages/cli/starters/nestjs/tsconfig.json +++ b/packages/cli/starters/nestjs/tsconfig.node.json @@ -11,6 +11,6 @@ "emitDecoratorMetadata": true, "outDir": "dist" }, - "include": ["**/*.ts", "**/*.vue"], - "exclude": ["node_modules", ".app", ".output"] + "include": ["**/*.ts"], + "exclude": ["node_modules", ".app", ".output", "app"] } diff --git a/packages/runable/src/config/load.ts b/packages/runable/src/config/load.ts index 43dfef4..31331e3 100644 --- a/packages/runable/src/config/load.ts +++ b/packages/runable/src/config/load.ts @@ -110,6 +110,8 @@ type RawEntry = { configFile?: string; /** The raw config as returned by `c12Load`, before `resolveConfig` runs on it. */ loaded: ModuleDefinition; + /** Whether this module was referenced through a relative filesystem path. */ + isLocalModule: boolean; /** Names of the configs whose `modules` list references this one. Empty for the root. */ dependents: Set; /** Position in discovery (completion) order — carried over to `_index` on the resolved config. */ @@ -225,6 +227,7 @@ async function loadAllConfigs( rawName: string, cwd: string | undefined, dependent?: string, + isLocalModule = false, ): Promise { const name = normalizeModuleName(rawName); @@ -246,26 +249,29 @@ async function loadAllConfigs( cwd: entryCwd, configFile, loaded: loaded ?? ({} as ModuleDefinition), + isLocalModule, dependents: new Set(), index: index++, }; entries.set(resolvedName, entry); - const childModules = (entry.loaded.modules ?? []).map((childName) => - resolveModuleName(childName, entryCwd), - ); + const childModules = (entry.loaded.modules ?? []).map((childName) => ({ + name: resolveModuleName(childName, entryCwd), + isLocal: childName.startsWith("."), + })); - for (const childName of childModules) { - addDependency(name, normalizeModuleName(childName)); + for (const child of childModules) { + addDependency(name, normalizeModuleName(child.name)); } await Promise.all( - childModules.map((childName) => + childModules.map((child) => load( - childName, - getModuleDir(childName, entryCwd, moduleDirCache), + child.name, + getModuleDir(child.name, entryCwd, moduleDirCache), resolvedName, + child.isLocal, ), ), ); @@ -277,6 +283,9 @@ async function loadAllConfigs( } const entry = await promise; + if (isLocalModule) { + entry.isLocalModule = true; + } moduleNameAliases?.set(normalizeDir(rawName), entry.name); moduleNameAliases?.set(normalizeModuleName(rawName), entry.name); if (dependent) entry.dependents.add(dependent); @@ -338,6 +347,7 @@ function resolveAllConfigs(entries: Map): { rConfig._configFile = entry.configFile; rConfig._isRunableModule = (entry.loaded as ResolvedConfig) ._isRunableModule as boolean; + rConfig._isLocalModule = entry.isLocalModule; rConfig._dependents = [...entry.dependents]; rConfig._index = entry.index; @@ -497,14 +507,16 @@ export interface ConfigGraph { } /** - * 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. + * Combines aliases from the main application and its local modules. Installed + * packages keep their aliases private. Local 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()) { + if (config._isRunableModule && !config._isLocalModule) continue; Object.assign(aliases, config.alias); } diff --git a/packages/runable/src/config/types.ts b/packages/runable/src/config/types.ts index b7b4132..8dfe59b 100644 --- a/packages/runable/src/config/types.ts +++ b/packages/runable/src/config/types.ts @@ -290,6 +290,9 @@ export type ResolvedConfig = { /** `true` if this config belongs to a Runable module rather than the root application. */ _isRunableModule?: boolean; + /** `true` when the module was referenced through a relative filesystem path. */ + _isLocalModule?: boolean; + /** * A module's resolved options (`defaults` merged with the consumer's * overrides) — only ever set for a config loaded on behalf of a `parent` diff --git a/packages/runable/src/utils/tsconfig.ts b/packages/runable/src/utils/tsconfig.ts index d941838..014affb 100644 --- a/packages/runable/src/utils/tsconfig.ts +++ b/packages/runable/src/utils/tsconfig.ts @@ -35,7 +35,7 @@ export const tsconfig = { }; export function writeTsConfig() { - const { output, appDir, alias, _configFile, _cwd } = useConfig(); + const { output, appDir, alias, _configFile } = useConfig(); Object.entries(alias ?? {}).forEach(([key, value]) => { if (key === "#build") return; @@ -55,7 +55,7 @@ export function writeTsConfig() { for (const config of useAllConfigs()) { if (!config._isRunableModule) continue; - if (!config._cwd.startsWith(_cwd)) continue; + if (!config._isLocalModule) continue; tsconfig.app.addInclude( normalizeDir(join(relative(output, config.appDir), "**/*")), diff --git a/playground/app/app.vue b/playground/app/app.vue new file mode 100644 index 0000000..13760b0 --- /dev/null +++ b/playground/app/app.vue @@ -0,0 +1,5 @@ + diff --git a/playground/app/components/hello.global.vue b/playground/app/components/hello.global.vue deleted file mode 100644 index 3e18e26..0000000 --- a/playground/app/components/hello.global.vue +++ /dev/null @@ -1,6 +0,0 @@ - diff --git a/playground/app/composables/test.ts b/playground/app/composables/test.ts deleted file mode 100644 index d0b7764..0000000 --- a/playground/app/composables/test.ts +++ /dev/null @@ -1,4 +0,0 @@ -export function useTest() { - useRuntime(); - console.log("composable teste ***********"); -} diff --git a/playground/app/css/index.css b/playground/app/css/index.css deleted file mode 100644 index 4296a80..0000000 --- a/playground/app/css/index.css +++ /dev/null @@ -1,3 +0,0 @@ -body { - background-color: yellowgreen; -} diff --git a/playground/app/layouts/admin.vue b/playground/app/layouts/admin.vue deleted file mode 100644 index 7c2aa3f..0000000 --- a/playground/app/layouts/admin.vue +++ /dev/null @@ -1,3 +0,0 @@ - diff --git a/playground/app/pages/hello/yes.vue b/playground/app/pages/hello/yes.vue deleted file mode 100644 index 413e37d..0000000 --- a/playground/app/pages/hello/yes.vue +++ /dev/null @@ -1,6 +0,0 @@ - diff --git a/playground/app/pages/index.vue b/playground/app/pages/index.vue deleted file mode 100644 index 06d9e97..0000000 --- a/playground/app/pages/index.vue +++ /dev/null @@ -1,377 +0,0 @@ - - - - - - - - diff --git a/playground/app/plugins/auth.ts b/playground/app/plugins/auth.ts deleted file mode 100644 index 46641fc..0000000 --- a/playground/app/plugins/auth.ts +++ /dev/null @@ -1,15 +0,0 @@ -export default defineVuePlugin({ - hooks: { - "app:mounted": () => { - console.log("++++++++++++++++++++++++++è_________"); - }, - }, - - setup: () => { - return { - provide: { - test: () => "hey is test", - }, - }; - }, -}); diff --git a/playground/app/tsconfig.json b/playground/app/tsconfig.json deleted file mode 100644 index 6f160fb..0000000 --- a/playground/app/tsconfig.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "files": [], - "references": [{ "path": "../.app/tsconfig.app.json" }] -} diff --git a/playground/content/docs/.navigation.yml b/playground/content/docs/.navigation.yml deleted file mode 100644 index 98463a2..0000000 --- a/playground/content/docs/.navigation.yml +++ /dev/null @@ -1 +0,0 @@ -path: documentation diff --git a/playground/content/docs/get-started/index.md b/playground/content/docs/get-started/index.md deleted file mode 100644 index b8e1be8..0000000 --- a/playground/content/docs/get-started/index.md +++ /dev/null @@ -1,59 +0,0 @@ ---- -title: 'Title of the page' -description: 'meta description of the page' ---- - -# Pluto - -**Pluto** (minor-planet designation: _134340 Pluto_) -is a -[dwarf planet](https://en.wikipedia.org/wiki/Dwarf_planet) -in the -[Kuiper belt](https://en.wikipedia.org/wiki/Kuiper_belt). - -## History - -### Sub History - -In the 1840s, -[Urbain Le Verrier](https://wikipedia.org/wiki/Urbain_Le_Verrier) -used Newtonian mechanics to predict the position of the -then-undiscovered planet -[Neptune](https://wikipedia.org/wiki/Neptune) -after analyzing perturbations in the orbit of -[Uranus](https://wikipedia.org/wiki/Uranus). - ---- - -Just a link: www.nasa.gov. - -- Lists -- [ ] todo -- [x] done - -A table: - -| a | b | -| --- | --- | - -
Show example - -```js -console.log("Hi pluto!"); -``` - -
- -::div ---- -class: "p-4" ---- - -Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. -:: - -::router-link ---- -to: "/hello" ---- -:: \ No newline at end of file diff --git a/playground/content/docs/get-started/intall.md b/playground/content/docs/get-started/intall.md deleted file mode 100644 index e69de29..0000000 diff --git a/playground/content/docs/get-started/usage.md b/playground/content/docs/get-started/usage.md deleted file mode 100644 index e69de29..0000000 diff --git a/playground/content/docs/index.md b/playground/content/docs/index.md deleted file mode 100644 index e69de29..0000000 diff --git a/playground/modules/content/syora.config.ts b/playground/modules/content/syora.config.ts deleted file mode 100644 index ee31a49..0000000 --- a/playground/modules/content/syora.config.ts +++ /dev/null @@ -1,4 +0,0 @@ -// packages/content/src/index.ts -import { defineConfig } from "runable"; - -export default defineConfig({}); diff --git a/playground/modules/test/runable.config.ts b/playground/modules/test/runable.config.ts new file mode 100644 index 0000000..e9bca81 --- /dev/null +++ b/playground/modules/test/runable.config.ts @@ -0,0 +1,5 @@ +import { defineModule } from "runable"; + +export default defineModule({ + configKey: "test01", +}); diff --git a/playground/public/favicon.svg b/playground/public/favicon.svg deleted file mode 100644 index 8431ca7..0000000 --- a/playground/public/favicon.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - diff --git a/playground/syora.config.ts b/playground/runable.config.ts similarity index 67% rename from playground/syora.config.ts rename to playground/runable.config.ts index 29bb0bf..d1eb283 100644 --- a/playground/syora.config.ts +++ b/playground/runable.config.ts @@ -1,10 +1,7 @@ import { defineConfig } from "runable"; -import { join } from "node:path"; export default defineConfig({ - // devtools: { enable: true }, - - modules: ["@runable/content"], + modules: ["./modules/test"], head: { title: "Runable/vue playground", @@ -18,5 +15,5 @@ export default defineConfig({ ssr: true, - content: {}, + test01: {}, }); diff --git a/playground/server.ts b/playground/server.ts index 8fe900e..37f122d 100644 --- a/playground/server.ts +++ b/playground/server.ts @@ -1 +1,16 @@ -import "./server/express/index.js"; +// server.ts +import Express from "express"; +import { express } from "runable/adapters/express"; + +const server = Express(); + +server.get("/api/health", (_req, res) => { + res.json({ status: "ok" }); +}); + +// The adapter initializes Runable once and serves the frontend. +server.use(express()); + +server.listen(3000, () => { + console.log("http://localhost:3000"); +}); diff --git a/playground/server/express/index.ts b/playground/server/express/index.ts deleted file mode 100644 index 89cc669..0000000 --- a/playground/server/express/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { express } from "runable/adapters/express"; -import Express from "express"; - -const app = Express(); -app.use(express()); - -app.listen(5173); diff --git a/playground/server/fastify/index.ts b/playground/server/fastify/index.ts deleted file mode 100644 index c05b945..0000000 --- a/playground/server/fastify/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import Fastify from "fastify"; -import { fastify } from "runable/adapters/fastify"; - -const app = Fastify(); -await app.register(fastify()); - -await app.listen({ port: 5173 }); diff --git a/playground/server/hono/index.ts b/playground/server/hono/index.ts deleted file mode 100644 index 30fcc17..0000000 --- a/playground/server/hono/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { serve as server, type HttpBindings } from "@hono/node-server"; -import { Hono } from "hono"; -import { hono } from "runable/adapters/hono"; - -const app = new Hono<{ Bindings: HttpBindings }>(); - -app.use("*", hono()); - -server({ fetch: app.fetch, port: 5173 }); diff --git a/playground/server/koa/index.ts b/playground/server/koa/index.ts deleted file mode 100644 index 295a096..0000000 --- a/playground/server/koa/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { koa } from "runable/adapters/koa"; -import Koa from "koa"; - -const app = new Koa(); -app.use(koa()); - -app.listen(5173); diff --git a/playground/server/nestjs/index.ts b/playground/server/nestjs/index.ts deleted file mode 100644 index ac0c6ab..0000000 --- a/playground/server/nestjs/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { NestFactory } from "@nestjs/core"; -import type { NestExpressApplication } from "@nestjs/platform-express"; -import { Module } from "@nestjs/common"; -import { nestjs } from "runable/adapters/nestjs"; - -@Module({}) -class AppModule {} - -async function bootstrap() { - const app = await NestFactory.create(AppModule); - app.use(nestjs()); - - await app.listen(5173); -} - -bootstrap(); diff --git a/playground/tsconfig.json b/playground/tsconfig.json index f4c4f24..92b0eee 100644 --- a/playground/tsconfig.json +++ b/playground/tsconfig.json @@ -1,8 +1,7 @@ { - "extends": "../tsconfig.base.json", - "compilerOptions": { - "paths": {} - }, - "include": ["./**/*.ts", "runable.config.ts"], - "exclude": ["dist", "node_modules"] + "files": [], + "references": [ + { "path": ".app/tsconfig.app.json" }, + { "path": "tsconfig.node.json" } + ] } diff --git a/playground/tsconfig.node.json b/playground/tsconfig.node.json new file mode 100644 index 0000000..a1102c1 --- /dev/null +++ b/playground/tsconfig.node.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "ES2023", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "strict": true, + "skipLibCheck": true, + "noEmit": true, + "types": ["node"] + }, + "include": ["**/*.ts", "**/*.vue"], + "exclude": ["node_modules", ".app", ".output"] +} diff --git a/tests/regressions/cli-create-module.test.ts b/tests/regressions/cli-create-module.test.ts index 61590e6..05dc4d9 100644 --- a/tests/regressions/cli-create-module.test.ts +++ b/tests/regressions/cli-create-module.test.ts @@ -1,6 +1,7 @@ -import { readFileSync } from "node:fs"; +import { existsSync, readFileSync } from "node:fs"; import path from "node:path"; import { describe, expect, it } from "vitest"; +import { cleanupFixtureDir, createFixtureDir } from "../fixtures.js"; import { REPO_ROOT } from "../helpers.js"; describe("runable create --module", () => { @@ -25,4 +26,75 @@ describe("runable create --module", () => { expect(selector).toContain('value: "starter"'); expect(selector).not.toContain('value: "module"'); }); + + it("creates a Runable playground that loads the module root", async () => { + const directory = createFixtureDir("cli-module-playground-"); + + try { + const { createModulePlayground } = await import( + "../../packages/cli/dist/commands/create/module.js" + ); + const { configurePnpmBuilds, createPackageJson } = await import( + "../../packages/cli/dist/commands/create/shared.js" + ); + await createModulePlayground(directory, { + moduleName: "test-module", + framework: "other", + createServerEntry: false, + }); + await createPackageJson(directory, "test-module"); + await configurePnpmBuilds("pnpm", directory); + + expect(existsSync(path.join(directory, "playground/app/app.vue"))).toBe( + true, + ); + expect( + existsSync(path.join(directory, "playground/app/pages/index.vue")), + ).toBe(true); + expect( + existsSync(path.join(directory, "playground/tsconfig.json")), + ).toBe(true); + expect( + existsSync(path.join(directory, "playground/tsconfig.node.json")), + ).toBe(true); + expect( + readFileSync( + path.join(directory, "playground/runable.config.ts"), + "utf8", + ), + ).toContain('modules: [".."]'); + + const packageJson = JSON.parse( + readFileSync(path.join(directory, "package.json"), "utf8"), + ); + expect(packageJson.workspaces).toEqual(["playground"]); + expect(packageJson.scripts).toMatchObject({ + "playground:prepare": "cd playground && runable prepare", + "playground:build": "cd playground && runable build", + }); + + const playgroundPackageJson = JSON.parse( + readFileSync(path.join(directory, "playground/package.json"), "utf8"), + ); + expect(playgroundPackageJson).toMatchObject({ + name: "test-module-playground", + private: true, + dependencies: { + "test-module": "*", + runable: expect.any(String), + vue: expect.any(String), + "vue-router": expect.any(String), + }, + devDependencies: { + "@runablejs/cli": expect.any(String), + typescript: expect.any(String), + }, + }); + expect( + readFileSync(path.join(directory, "pnpm-workspace.yaml"), "utf8"), + ).toContain("- playground"); + } finally { + cleanupFixtureDir(directory); + } + }); }); diff --git a/tests/regressions/cli-starters.test.ts b/tests/regressions/cli-starters.test.ts index f6c6565..55bf3bd 100644 --- a/tests/regressions/cli-starters.test.ts +++ b/tests/regressions/cli-starters.test.ts @@ -41,6 +41,19 @@ describe("CLI starter templates", () => { 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); + expect(existsSync(join(target, "app/tsconfig.json"))).toBe(false); + + const tsconfig = JSON.parse( + readFileSync(join(target, "tsconfig.json"), "utf8"), + ); + expect(tsconfig).toEqual({ + files: [], + references: [ + { path: ".app/tsconfig.app.json" }, + { path: "tsconfig.node.json" }, + ], + }); + expect(existsSync(join(target, "tsconfig.node.json"))).toBe(true); const indexPage = readFileSync( join(target, "app/pages/index.vue"), diff --git a/tests/regressions/module-aliases.test.ts b/tests/regressions/module-aliases.test.ts index 897dccc..58aee85 100644 --- a/tests/regressions/module-aliases.test.ts +++ b/tests/regressions/module-aliases.test.ts @@ -68,6 +68,101 @@ export default defineModule({ "@module": ["../module/app"], "@shared": ["../app/shared"], }); + expect(generated.include).toContain("../module/app/**/*"); + } finally { + cleanupFixtureDir(directory); + } + }); + + it('includes a local parent module declared with modules: [".."]', async () => { + const directory = createFixtureDir("parent-module-tsconfig-"); + const application = path.join(directory, "playground"); + + try { + linkWorkspacePackage(directory, "runable", "packages/runable"); + writeFixtureFile( + directory, + "runable.config.ts", + `import { defineModule } from "runable"; + +export default defineModule({}); +`, + ); + writeFixtureFile( + directory, + "playground/runable.config.ts", + `import { defineConfig } from "runable"; + +export default defineConfig({ modules: [".."] }); +`, + ); + + process.chdir(application); + vi.resetModules(); + const { loadConfig, writeTsConfig } = await import("runable"); + await loadConfig(); + writeTsConfig(); + + const generated = JSON.parse( + readFileSync(path.join(application, ".app/tsconfig.app.json"), "utf8"), + ); + expect(generated.include).toContain("../../app/**/*"); + } finally { + cleanupFixtureDir(directory); + } + }); + + it("does not inherit aliases from installed modules", async () => { + const directory = createFixtureDir("installed-module-aliases-"); + + try { + linkWorkspacePackage(directory, "runable", "packages/runable"); + writeFixtureFile( + directory, + "runable.config.ts", + `import { defineConfig } from "runable"; + +export default defineConfig({ modules: ["installed-module"] }); +`, + ); + writeFixtureFile( + directory, + "node_modules/installed-module/package.json", + JSON.stringify({ + name: "installed-module", + type: "module", + exports: "./dist/index.js", + }), + ); + writeFixtureFile( + directory, + "node_modules/installed-module/dist/index.js", + "export {};\n", + ); + writeFixtureFile( + directory, + "node_modules/installed-module/dist/runable.config.js", + `import path from "node:path"; +import { defineModule } from "runable"; + +export default defineModule({ + alias: { "@installed": path.join(import.meta.dirname, "app") }, +}); +`, + ); + + process.chdir(directory); + vi.resetModules(); + const { loadConfig, useConfig, writeTsConfig } = await import("runable"); + await loadConfig(); + writeTsConfig(); + + expect(useConfig().alias).not.toHaveProperty("@installed"); + + const generated = JSON.parse( + readFileSync(path.join(directory, ".app/tsconfig.app.json"), "utf8"), + ); + expect(generated.compilerOptions.paths).not.toHaveProperty("@installed"); } finally { cleanupFixtureDir(directory); } diff --git a/website/app/pages/changelog.vue b/website/app/pages/changelog.vue index c1da190..4b99072 100644 --- a/website/app/pages/changelog.vue +++ b/website/app/pages/changelog.vue @@ -1,12 +1,30 @@