@alxia/create 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +101 -0
- package/dist/args.d.ts +19 -0
- package/dist/args.d.ts.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +599 -0
- package/dist/index.js.map +19 -0
- package/dist/io.d.ts +18 -0
- package/dist/io.d.ts.map +1 -0
- package/dist/main.d.ts +9 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/registry.d.ts +44 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/target.d.ts +7 -0
- package/dist/target.d.ts.map +1 -0
- package/dist/templates/api.d.ts +16 -0
- package/dist/templates/api.d.ts.map +1 -0
- package/dist/templates/react-router.d.ts +26 -0
- package/dist/templates/react-router.d.ts.map +1 -0
- package/dist/versions.d.ts +27 -0
- package/dist/versions.d.ts.map +1 -0
- package/dist/write.d.ts +6 -0
- package/dist/write.d.ts.map +1 -0
- package/docs/README.md +12 -0
- package/docs/guide.md +253 -0
- package/docs/roadmap.md +39 -0
- package/docs/troubleshooting.md +278 -0
- package/package.json +46 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/main.ts", "../src/args.ts", "../src/target.ts", "../src/templates/react-router.ts", "../src/versions.ts", "../src/write.ts", "../src/registry.ts", "../src/templates/api.ts", "../src/io.ts", "../src/index.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * The `create-alxia` command: its arguments or answers, the project it\n * writes, what it prints, and its exit code. `index.ts` runs it on the\n * process's own streams; a spec runs it on its own.\n */\nimport { stat } from 'node:fs/promises';\nimport { relative, resolve } from 'node:path';\nimport { isTemplate, NAME, parseArgs, TEMPLATES, type Template } from './args';\nimport type { Io } from './io';\nimport { clean, refusal } from './target';\nimport { ScaffoldChanged } from './templates/react-router';\nimport { alxiaRanges } from './versions';\nimport { write } from './write';\n\nexport { NAME } from './args';\nexport type { Io } from './io';\nexport { processIo } from './io';\nexport { packageName, refusal } from './target';\n\nexport const USAGE = `Usage: bun create @alxia [dir] [options]\n\nWrites a new alxia app in dir, which must be empty or not exist yet. Asks\nfor what is not given.\n\nOptions:\n -t, --template <name> api an alxia app with Zod, a spec and the typed client\n react-router React Router's official template, served by alxia\n --no-install write the files, skip bun install\n -h, --help show this help`;\n\n/** Runs the command in `cwd`; resolves to the process's exit code. */\nexport async function main(\n\targs: readonly string[],\n\tcwd: string,\n\tio: Io,\n): Promise<number> {\n\tconst options = parseArgs(args);\n\tif ('error' in options) {\n\t\tio.err(`${NAME}: ${options.error}\\n\\n${USAGE}`);\n\t\treturn 1;\n\t}\n\tif (options.help) {\n\t\tio.out(USAGE);\n\t\treturn 0;\n\t}\n\n\tconst dir =\n\t\toptions.dir ?? io.ask('Where should the project go?', 'alxia-app');\n\tif (dir === null) {\n\t\tio.err(`${NAME}: cancelled, nothing written.`);\n\t\treturn 1;\n\t}\n\tif (dir === undefined) {\n\t\tio.err(\n\t\t\t`${NAME}: no directory given, and no terminal to ask in.\\n\\n${USAGE}`,\n\t\t);\n\t\treturn 1;\n\t}\n\tconst template = options.template ?? askTemplate(io);\n\tif (template === null) {\n\t\tio.err(`${NAME}: cancelled, nothing written.`);\n\t\treturn 1;\n\t}\n\tif (typeof template === 'object') {\n\t\tio.err(`${NAME}: ${template.error}\\n\\n${USAGE}`);\n\t\treturn 1;\n\t}\n\n\tconst target = resolve(cwd, dir);\n\tconst refused = await refusal(target, dir);\n\tif (refused) {\n\t\tio.err(`${NAME}: ${refused}`);\n\t\treturn 1;\n\t}\n\n\tconst existed = (await stat(target).catch(() => undefined)) !== undefined;\n\tconst made = await alxiaRanges()\n\t\t.then((alxia) => write(target, template, alxia, io))\n\t\t.catch(async (error) => {\n\t\t\t// The directory was empty or absent: what is in it now is ours.\n\t\t\tawait clean(target, existed);\n\t\t\tio.err(\n\t\t\t\t`${NAME}: ${error instanceof ScaffoldChanged ? '' : 'failed: '}${error instanceof Error ? error.message : String(error)}`,\n\t\t\t);\n\t\t\treturn false;\n\t\t});\n\tif (!made) return 1;\n\n\tlet installed = true;\n\tif (options.install) {\n\t\tio.out('\\nInstalling dependencies with bun install...');\n\t\tinstalled = (await io.run([process.execPath, 'install'], target)) === 0;\n\t\tif (!installed)\n\t\t\tio.err(`${NAME}: bun install failed; the files are written.`);\n\t}\n\tio.out(\n\t\tdone(relative(cwd, target) || '.', template, options.install && installed),\n\t);\n\treturn installed ? 0 : 1;\n}\n\n/** The last thing printed: what was written, and the commands to run next. */\nfunction done(where: string, template: Template, installed: boolean): string {\n\tconst steps = [\n\t\twhere === '.' ? undefined : `cd ${where}`,\n\t\tinstalled ? undefined : 'bun install',\n\t\t'bun dev',\n\t].filter((step) => step !== undefined);\n\treturn `\\nDone: ${where === '.' ? 'this directory' : where} holds the ${template} template. Next:\\n\\n${steps\n\t\t.map((step) => ` ${step}`)\n\t\t.join('\\n')}\\n`;\n}\n\n/** The template answered; null when cancelled. */\nfunction askTemplate(io: Io): Template | { error: string } | null {\n\tconst answer = io.ask(`Which template? ${TEMPLATES.join(' or ')}`, 'api');\n\tif (answer === null) return null;\n\tif (answer === undefined) {\n\t\treturn { error: 'no --template given, and no terminal to ask in.' };\n\t}\n\treturn isTemplate(answer)\n\t\t? answer\n\t\t: { error: `unknown template ${answer}: use ${TEMPLATES.join(' or ')}.` };\n}\n",
|
|
6
|
+
"/** The command line of `create-alxia`, read into what to create. */\n\n/** The command's name, which starts every line it prints to stderr. */\nexport const NAME = 'create-alxia';\n\n/** The templates, in the order the prompt lists them. */\nexport const TEMPLATES = ['api', 'react-router'] as const;\nexport type Template = (typeof TEMPLATES)[number];\n\nexport function isTemplate(value: string): value is Template {\n\treturn (TEMPLATES as readonly string[]).includes(value);\n}\n\n/** What the arguments asked for. A missing `dir` or `template` is prompted. */\nexport interface Options {\n\treadonly dir?: string;\n\treadonly template?: Template;\n\treadonly install: boolean;\n\treadonly help: boolean;\n}\n\n/** The arguments as options, or the message that refuses them. */\nexport function parseArgs(\n\targs: readonly string[],\n): Options | { readonly error: string } {\n\tlet dir: string | undefined;\n\tlet template: Template | undefined;\n\tlet install = true;\n\tlet help = false;\n\tfor (let i = 0; i < args.length; i++) {\n\t\tconst arg = args[i] as string;\n\t\tif (arg === '--help' || arg === '-h') help = true;\n\t\telse if (arg === '--no-install') install = false;\n\t\telse if (\n\t\t\targ === '--template' ||\n\t\t\targ === '-t' ||\n\t\t\targ.startsWith('--template=')\n\t\t) {\n\t\t\tconst value = arg.includes('=')\n\t\t\t\t? arg.slice(arg.indexOf('=') + 1)\n\t\t\t\t: args[++i];\n\t\t\tif (value === undefined || value === '') {\n\t\t\t\treturn { error: `${arg} needs a template: ${TEMPLATES.join(' or ')}.` };\n\t\t\t}\n\t\t\tif (!isTemplate(value)) {\n\t\t\t\treturn {\n\t\t\t\t\terror: `unknown template ${value}: use ${TEMPLATES.join(' or ')}.`,\n\t\t\t\t};\n\t\t\t}\n\t\t\ttemplate = value;\n\t\t} else if (arg.startsWith('-')) return { error: `unknown option ${arg}.` };\n\t\telse if (dir === undefined) dir = arg;\n\t\telse return { error: `one directory only, given ${dir} and ${arg}.` };\n\t}\n\treturn {\n\t\t...(dir === undefined ? {} : { dir }),\n\t\t...(template === undefined ? {} : { template }),\n\t\tinstall,\n\t\thelp,\n\t};\n}\n",
|
|
7
|
+
"/** The directory a project is written into: its name, whether it may be, and undoing it. */\nimport { readdir, rm, stat } from 'node:fs/promises';\nimport { basename, join } from 'node:path';\n\n/** A directory name as a package name: lowercased, `-` for the rest. */\nexport function packageName(dir: string): string {\n\tconst name = basename(dir)\n\t\t.toLowerCase()\n\t\t.replace(/[^a-z0-9._~-]+/g, '-')\n\t\t.replace(/^[._-]+|-+$/g, '');\n\treturn name || 'alxia-app';\n}\n\n/** Why `target` cannot take a new project, or undefined when it can. */\nexport async function refusal(\n\ttarget: string,\n\tshown: string = target,\n): Promise<string | undefined> {\n\tconst info = await stat(target).catch(() => undefined);\n\tif (!info) return undefined;\n\tif (!info.isDirectory()) return `${shown} exists and is not a directory.`;\n\tconst entries = (await readdir(target)).sort();\n\tif (entries.length === 0) return undefined;\n\treturn (\n\t\t`${shown} is not empty (${entries.slice(0, 3).join(', ')}${entries.length > 3 ? ', ...' : ''}), ` +\n\t\t'and create-alxia writes only into an empty directory. Choose another directory, or empty this one.'\n\t);\n}\n\n/** Removes what was written: the directory, or what is in the one that was there. */\nexport async function clean(target: string, existed: boolean): Promise<void> {\n\tif (!existed) {\n\t\tawait rm(target, { recursive: true, force: true });\n\t\treturn;\n\t}\n\tfor (const entry of await readdir(target).catch(() => [])) {\n\t\tawait rm(join(target, entry), { recursive: true, force: true });\n\t}\n}\n",
|
|
8
|
+
"/**\n * The `react-router` template: React Router's official one, from its own\n * `create-react-router`, then alxia added as `examples/react-router` adds it\n * — `@alxia/core` and `@alxia/react-router` as dependencies, `alxia()` in\n * the Vite plugins, `start` running the build on Bun, and a `bunfig.toml`\n * that starts React Router's CLI on Bun. Each edit checks the file is the one\n * it expects first, and refuses rather than write a project that does not\n * work.\n */\nimport { join } from 'node:path';\nimport type { Manifest } from '../registry';\nimport { type AlxiaPackage, highestMajor, PEER_RANGES } from '../versions';\n\n/**\n * The scaffold's command line: the newest `create-react-router` of the\n * highest React Router major `@alxia/react-router` accepts, with no prompt,\n * no install and no git repository — the install is ours, once alxia is in.\n */\nexport function scaffoldCommand(dir: string): string[] {\n\treturn [\n\t\t`create-react-router@${highestMajor(PEER_RANGES['react-router'])}`,\n\t\tdir,\n\t\t'--yes',\n\t\t'--no-install',\n\t\t'--no-git-init',\n\t\t'--no-agent-skills',\n\t\t'--no-motion',\n\t];\n}\n\n/** The scaffold is not what the edits expect: nothing more is written, and `main` removes it. */\nexport class ScaffoldChanged extends Error {\n\tconstructor(file: string, expected: string) {\n\t\tsuper(\n\t\t\t`create-react-router's ${file} is not what this @alxia/create expects: ${expected}. ` +\n\t\t\t\t'Nothing was kept. Try the newest @alxia/create, or add @alxia/react-router to a React Router app by hand, as its README shows.',\n\t\t);\n\t\tthis.name = 'ScaffoldChanged';\n\t}\n}\n\nexport const BUNFIG = `[run]\n# The React Router CLI is a node script: this starts it on Bun, which\n# alxia's server needs, even where a node is installed.\nbun = true\n`;\n\nconst IMPORT = 'import { alxia } from \"@alxia/react-router/vite\";\\n';\n\n/** `vite.config.ts` with `alxia()` after `reactRouter()`. */\nexport function addPlugin(config: string): string {\n\tconst calls = config.match(/\\breactRouter\\(\\)/g) ?? [];\n\tif (\n\t\t!config.includes('from \"@react-router/dev/vite\"') ||\n\t\t!/plugins:\\s*\\[[^\\]]*\\breactRouter\\(\\)[^\\]]*\\]/.test(config) ||\n\t\tcalls.length !== 1\n\t) {\n\t\tthrow new ScaffoldChanged(\n\t\t\t'vite.config.ts',\n\t\t\t'reactRouter() from \"@react-router/dev/vite\", called once in plugins: [...]',\n\t\t);\n\t}\n\tif (config.includes('@alxia/react-router/vite')) {\n\t\tthrow new ScaffoldChanged(\n\t\t\t'vite.config.ts',\n\t\t\t'it already imports @alxia/react-router/vite',\n\t\t);\n\t}\n\treturn IMPORT + config.replace(/\\breactRouter\\(\\)/, 'reactRouter(), alxia()');\n}\n\n/** The manifest with alxia's dependencies and `start` on Bun. */\nexport function addAlxia(\n\tmanifest: Manifest,\n\talxia: Record<AlxiaPackage, string>,\n): Manifest {\n\tconst scripts = manifest['scripts'] as Record<string, string> | undefined;\n\tif (\n\t\tscripts?.['dev'] !== 'react-router dev' ||\n\t\tscripts['build'] !== 'react-router build' ||\n\t\tscripts['start'] === undefined ||\n\t\tmanifest.dependencies?.['react-router'] === undefined\n\t) {\n\t\tthrow new ScaffoldChanged(\n\t\t\t'package.json',\n\t\t\t'react-router in its dependencies, and the scripts `dev: react-router dev`, `build: react-router build` and a `start`',\n\t\t);\n\t}\n\tconst dependencies = sorted({\n\t\t...manifest.dependencies,\n\t\t'@alxia/core': alxia['@alxia/core'],\n\t\t'@alxia/react-router': alxia['@alxia/react-router'],\n\t});\n\treturn {\n\t\t...manifest,\n\t\tscripts: { ...scripts, start: 'bun build/server/index.js' },\n\t\tdependencies,\n\t};\n}\n\nfunction sorted(deps: Record<string, string>): Record<string, string> {\n\treturn Object.fromEntries(\n\t\tObject.entries(deps).sort(([a], [b]) => (a < b ? -1 : 1)),\n\t);\n}\n\n/**\n * Reads what the scaffold wrote in `dir` and checks it, without writing:\n * the manifest with alxia in, and the files to write beside it.\n */\nexport async function alxiaLayer(\n\tdir: string,\n\talxia: Record<AlxiaPackage, string>,\n): Promise<{ manifest: Manifest; files: Record<string, string> }> {\n\tconst read = async (file: string) => {\n\t\tconst handle = Bun.file(join(dir, file));\n\t\tif (!(await handle.exists())) {\n\t\t\tthrow new ScaffoldChanged(file, 'it is missing');\n\t\t}\n\t\treturn handle.text();\n\t};\n\tconst manifest = addAlxia(JSON.parse(await read('package.json')), alxia);\n\tconst config = addPlugin(await read('vite.config.ts'));\n\tif (await Bun.file(join(dir, 'bunfig.toml')).exists()) {\n\t\tthrow new ScaffoldChanged('bunfig.toml', 'none, and there is one');\n\t}\n\treturn {\n\t\tmanifest,\n\t\tfiles: { 'vite.config.ts': config, 'bunfig.toml': BUNFIG },\n\t};\n}\n",
|
|
9
|
+
"/**\n * The versions a generated project starts from: alxia's own packages at the\n * versions this release of `@alxia/create` was published beside, and the\n * ranges alxia's peers accept, which the registry's newest versions must\n * stay within.\n */\nimport { dirname } from 'node:path';\n\n/** The `@alxia/*` packages a template depends on. */\nexport type AlxiaPackage =\n\t| '@alxia/client'\n\t| '@alxia/core'\n\t| '@alxia/react-router';\n\nconst ALXIA_PACKAGES: readonly AlxiaPackage[] = [\n\t'@alxia/client',\n\t'@alxia/core',\n\t'@alxia/react-router',\n];\n\n/**\n * The peer ranges of alxia's packages that a template's other dependencies\n * must stay within, by the package they constrain: `typescript` is every\n * package's, `zod` is `@alxia/zod`'s, `react-router` and `vite` are\n * `@alxia/react-router`'s. `versions.spec.ts` checks each against the\n * package that declares it, so a widened peer fails there until it is\n * widened here too.\n */\nexport const PEER_RANGES = {\n\ttypescript: '^6.0.3 || ^7.0.0',\n\tzod: '^4.2.0',\n\t'react-router': '^8.0.0',\n\tvite: '^7.0.0 || ^8.0.0',\n} as const;\n\n/** The highest major a range such as `^7.0.0 || ^8.0.0` names. */\nexport function highestMajor(range: string): number {\n\tconst majors = [...range.matchAll(/(?:^|[\\s|^~>=])(\\d+)/g)].map((match) =>\n\t\tNumber(match[1]),\n\t);\n\tif (majors.length === 0) throw new Error(`no major in range ${range}`);\n\treturn Math.max(...majors);\n}\n\n/**\n * The range a generated project declares for each `@alxia/*` package: what\n * this package's own `package.json` says. Published, that is `^<version>`,\n * which `bun publish` writes in place of `workspace:^` from the version\n * beside it in the workspace. Inside the workspace, `workspace:^` is\n * resolved the same way, from the sibling's installed `package.json`.\n */\nexport async function alxiaRanges(\n\tmanifestUrl: URL = new URL('../package.json', import.meta.url),\n): Promise<Record<AlxiaPackage, string>> {\n\tconst manifest = await Bun.file(manifestUrl).json();\n\tconst declared: Record<string, string> = manifest.devDependencies ?? {};\n\tconst ranges = {} as Record<AlxiaPackage, string>;\n\tfor (const name of ALXIA_PACKAGES) {\n\t\tconst range = declared[name];\n\t\tif (range === undefined) {\n\t\t\tthrow new Error(`@alxia/create: its package.json names no ${name}`);\n\t\t}\n\t\tranges[name] = range.startsWith('workspace:')\n\t\t\t? await workspaceRange(name, range, dirname(manifestUrl.pathname))\n\t\t\t: range;\n\t}\n\treturn ranges;\n}\n\n/** `workspace:^` as `bun publish` rewrites it: `^` and the sibling's version. */\nasync function workspaceRange(\n\tname: string,\n\trange: string,\n\tfrom: string,\n): Promise<string> {\n\tconst file = Bun.resolveSync(`${name}/package.json`, from);\n\tconst { version } = await Bun.file(file).json();\n\tconst prefix = range.slice('workspace:'.length);\n\treturn prefix === '*' ? version : `${prefix}${version}`;\n}\n",
|
|
10
|
+
"/** Writes a template's project, its dependencies moved to the registry's newest. */\nimport { mkdir } from 'node:fs/promises';\nimport { basename, dirname, join } from 'node:path';\nimport { NAME, type Template } from './args';\nimport type { Io } from './io';\nimport { bumpDependencies, type Manifest, registryUrl } from './registry';\nimport { packageName } from './target';\nimport { apiFiles, apiManifest } from './templates/api';\nimport { alxiaLayer, scaffoldCommand } from './templates/react-router';\nimport type { AlxiaPackage } from './versions';\n\n/** Writes the project into `target`; throws on a scaffold that changed. */\nexport async function write(\n\ttarget: string,\n\ttemplate: Template,\n\talxia: Record<AlxiaPackage, string>,\n\tio: Io,\n): Promise<true> {\n\tlet manifest: Manifest;\n\tlet files: Record<string, string>;\n\tif (template === 'api') {\n\t\tmanifest = apiManifest(packageName(target), alxia);\n\t\tfiles = apiFiles(packageName(target));\n\t} else {\n\t\tawait mkdir(dirname(target), { recursive: true });\n\t\tconst code = await io.run(\n\t\t\t[process.execPath, 'x', ...scaffoldCommand(basename(target))],\n\t\t\tdirname(target),\n\t\t);\n\t\tif (code !== 0) {\n\t\t\tthrow new Error(`create-react-router exited with ${code}.`);\n\t\t}\n\t\t({ manifest, files } = await alxiaLayer(target, alxia));\n\t}\n\n\tio.out('Resolving the newest versions alxia accepts...');\n\tconst bumped = await bumpDependencies(\n\t\tmanifest,\n\t\t{ url: registryUrl(io.env), ...(io.fetch ? { fetch: io.fetch } : {}) },\n\t\tnew Set(Object.keys(alxia)),\n\t);\n\tfor (const line of bumped.moved) io.out(` ${line}`);\n\tfor (const line of [...bumped.held, ...bumped.unmatched]) io.out(` ${line}`);\n\tif (bumped.failed.length > 0) {\n\t\tio.err(\n\t\t\t`${NAME}: warning: the registry did not answer for ${bumped.failed.join(', ')}; ` +\n\t\t\t\t'kept the versions the template ships.',\n\t\t);\n\t}\n\n\tfiles['package.json'] = `${JSON.stringify(manifest, null, 2)}\\n`;\n\tfor (const [file, content] of Object.entries(files)) {\n\t\tawait Bun.write(join(target, file), content);\n\t}\n\treturn true;\n}\n",
|
|
11
|
+
"/**\n * Moves a generated project's dependencies to their newest versions on the\n * registry, within what alxia accepts: a template ships the versions it was\n * written with, and React Router's lags behind its own releases.\n */\nimport { PEER_RANGES } from './versions';\n\n/** The dependency fields of a manifest, as a template writes them. */\nexport interface Manifest {\n\tdependencies?: Record<string, string>;\n\tdevDependencies?: Record<string, string>;\n\t[key: string]: unknown;\n}\n\n/** How to reach the registry: its URL, and the `fetch` to call it with. */\nexport interface Registry {\n\treadonly url: string;\n\treadonly fetch?: (request: Request) => Promise<Response>;\n\t/** How long one package's metadata may take, in milliseconds. */\n\treadonly timeout?: number;\n}\n\n/** What a bump changed, and what it could not. */\nexport interface Bumped {\n\t/** One line per dependency moved: `vite ^8.0.3 -> ^8.3.2`. */\n\treadonly moved: string[];\n\t/** A newer major that was left out, because a range refuses it. */\n\treadonly held: string[];\n\t/** A package whose metadata did not arrive: its version is unchanged. */\n\treadonly failed: string[];\n\t/** A package with no release within its range: its version is unchanged. */\n\treadonly unmatched: string[];\n}\n\n/** The registry to resolve against: the one Bun or npm was given, or npm's. */\nexport function registryUrl(env: Record<string, string | undefined>): string {\n\tconst url =\n\t\tenv['BUN_CONFIG_REGISTRY'] ||\n\t\tenv['npm_config_registry'] ||\n\t\t'https://registry.npmjs.org';\n\treturn url.replace(/\\/+$/, '');\n}\n\n/**\n * The range alxia's peers hold `name` to — React Router's packages follow\n * `react-router`'s — or undefined when none does, and npm's `latest` is\n * taken.\n */\nexport function allowedRange(name: string): string | undefined {\n\tif (name === 'react-router' || name.startsWith('@react-router/')) {\n\t\treturn PEER_RANGES['react-router'];\n\t}\n\treturn name in PEER_RANGES\n\t\t? PEER_RANGES[name as keyof typeof PEER_RANGES]\n\t\t: undefined;\n}\n\ninterface Packument {\n\treadonly 'dist-tags'?: Record<string, string>;\n\treadonly versions?: Record<string, unknown>;\n}\n\n/** The newest release (not a prerelease) of `versions` within `range`. */\nexport function newestWithin(\n\tversions: readonly string[],\n\trange: string,\n): string | undefined {\n\treturn versions\n\t\t.filter((v) => !v.includes('-') && Bun.semver.satisfies(v, range))\n\t\t.sort(Bun.semver.order)\n\t\t.at(-1);\n}\n\nasync function packument(name: string, registry: Registry): Promise<Packument> {\n\tconst call = registry.fetch ?? ((request: Request) => fetch(request));\n\tconst response = await call(\n\t\tnew Request(`${registry.url}/${name.replace('/', '%2f')}`, {\n\t\t\theaders: { accept: 'application/vnd.npm.install-v1+json' },\n\t\t\tsignal: AbortSignal.timeout(registry.timeout ?? 5000),\n\t\t}),\n\t);\n\tif (!response.ok) throw new Error(`${response.status}`);\n\treturn (await response.json()) as Packument;\n}\n\n/** Within `range`, the newest; with none, npm's `latest`. */\nfunction choose(\n\tversions: readonly string[],\n\trange: string | undefined,\n\tlatest: string | undefined,\n): string | undefined {\n\tif (range !== undefined) return newestWithin(versions, range);\n\treturn latest && versions.includes(latest)\n\t\t? latest\n\t\t: newestWithin(versions, '*');\n}\n\n/**\n * Rewrites every dependency of `manifest` but those in `keep` to `^` its\n * newest version: within the range alxia's peers hold it to, or npm's\n * `latest` when none does. React Router's own packages take the\n * version `react-router` resolved to, when they have it: `@react-router/node`\n * pins `react-router` exactly. A package whose metadata fails to arrive keeps\n * its version, and is named in `failed`.\n */\nexport async function bumpDependencies(\n\tmanifest: Manifest,\n\tregistry: Registry,\n\tkeep: ReadonlySet<string> = new Set(),\n): Promise<Bumped> {\n\tconst fields = ['dependencies', 'devDependencies'] as const;\n\tconst names = fields.flatMap((field) =>\n\t\tObject.keys(manifest[field] ?? {}).filter((name) => !keep.has(name)),\n\t);\n\tconst fetched = new Map(\n\t\tawait Promise.all(\n\t\t\tnames.map(\n\t\t\t\tasync (name) =>\n\t\t\t\t\t[\n\t\t\t\t\t\tname,\n\t\t\t\t\t\tawait packument(name, registry).catch(() => undefined),\n\t\t\t\t\t] as const,\n\t\t\t),\n\t\t),\n\t);\n\tconst moved: string[] = [];\n\tconst held: string[] = [];\n\tconst failed: string[] = [];\n\tconst unmatched: string[] = [];\n\tconst chosen = new Map<string, string>();\n\t// react-router first: its siblings follow the version it settles on.\n\tconst ordered = [...names].sort(\n\t\t(a, b) => Number(b === 'react-router') - Number(a === 'react-router'),\n\t);\n\tfor (const name of ordered) {\n\t\tconst field = fields.find((f) => manifest[f]?.[name] !== undefined);\n\t\tconst deps = field && manifest[field];\n\t\tconst current = deps?.[name];\n\t\tif (!deps || current === undefined) continue;\n\t\tconst meta = fetched.get(name);\n\t\tif (!meta?.versions) {\n\t\t\tfailed.push(name);\n\t\t\tcontinue;\n\t\t}\n\t\tconst versions = Object.keys(meta.versions);\n\t\tconst range = allowedRange(name);\n\t\tconst latest = meta['dist-tags']?.['latest'];\n\t\tconst follow = chosen.get('react-router');\n\t\tconst version =\n\t\t\tname.startsWith('@react-router/') && follow && versions.includes(follow)\n\t\t\t\t? follow\n\t\t\t\t: choose(versions, range, latest);\n\t\tif (version === undefined) {\n\t\t\tunmatched.push(\n\t\t\t\t`${name}: no release within ${range ?? 'any range'}; kept ${current}`,\n\t\t\t);\n\t\t\tcontinue;\n\t\t}\n\t\tchosen.set(name, version);\n\t\tif (\n\t\t\trange !== undefined &&\n\t\t\tlatest &&\n\t\t\tBun.semver.order(latest, version) > 0 &&\n\t\t\t!Bun.semver.satisfies(latest, range)\n\t\t) {\n\t\t\theld.push(\n\t\t\t\t`${name}: kept to ${range}, where the newest is ${version}; npm's latest, ${latest}, is outside it`,\n\t\t\t);\n\t\t}\n\t\tconst next = `^${version}`;\n\t\tif (next !== current) moved.push(`${name} ${current} -> ${next}`);\n\t\tdeps[name] = next;\n\t}\n\treturn { moved, held, failed, unmatched };\n}\n",
|
|
12
|
+
"/**\n * The `api` template: an alxia app with Zod, one validated route behind a\n * hook of its own, a spec that calls it in process and through the typed\n * client, and the scripts to run, test, typecheck and build it.\n */\nimport type { Manifest } from '../registry';\nimport type { AlxiaPackage } from '../versions';\n\n/**\n * The template's manifest. The versions of `zod`, `typescript` and\n * `@types/bun` are where it starts: `bumpDependencies` moves each to the\n * registry's newest within alxia's peer ranges.\n */\nexport function apiManifest(\n\tname: string,\n\talxia: Record<AlxiaPackage, string>,\n): Manifest {\n\treturn {\n\t\tname,\n\t\tprivate: true,\n\t\ttype: 'module',\n\t\tscripts: {\n\t\t\tdev: 'bun --watch src/server.ts',\n\t\t\tbuild: 'bun build src/server.ts --target=bun --outdir=dist',\n\t\t\tstart: 'bun dist/server.js',\n\t\t\ttest: 'bun test',\n\t\t\ttypecheck: 'tsc --noEmit',\n\t\t},\n\t\tdependencies: {\n\t\t\t'@alxia/core': alxia['@alxia/core'],\n\t\t\tzod: '^4.2.0',\n\t\t},\n\t\tdevDependencies: {\n\t\t\t'@alxia/client': alxia['@alxia/client'],\n\t\t\t'@types/bun': '^1.4.2',\n\t\t\ttypescript: '^6.0.3',\n\t\t},\n\t};\n}\n\nconst APP = `import { alxia, defineHook } from '@alxia/core';\nimport { z } from 'zod';\n\nconst Todo = z.object({ id: z.number(), title: z.string(), done: z.boolean() });\nconst NewTodo = z.object({ title: z.string().min(1) });\n\n/** Set API_KEY in the environment: this default is for development. */\nexport const apiKey = Bun.env['API_KEY'] ?? 'dev-key';\n\n// A hook of the routes it is given to: it answers 401 without the key, and\n// that 401 joins the type of each, so the client reads it.\nconst requireKey = defineHook(({ request, reply }) =>\n\trequest.headers.get('x-api-key') === apiKey\n\t\t? undefined\n\t\t: reply(401, { error: 'unauthorized' as const }),\n);\n\nconst todos: z.infer<typeof Todo>[] = [];\n\nexport const app = alxia()\n\t.decorate({ todos })\n\t.post(\n\t\t'/todos',\n\t\t[requireKey],\n\t\t{ body: NewTodo, response: { 201: Todo } },\n\t\t({ body, todos, reply }) => {\n\t\t\tconst todo = { id: todos.length + 1, title: body.title, done: false };\n\t\t\ttodos.push(todo);\n\t\t\treturn reply.created(todo);\n\t\t},\n\t);\n\nexport type App = typeof app;\n`;\n\nconst SERVER = `import { app } from './app';\n\nconst server = app.listen(Number(Bun.env['PORT'] ?? 3000));\nconsole.log(\\`listening on \\${server.url}\\`);\n`;\n\nconst SPEC = `import { expect, test } from 'bun:test';\nimport { client } from '@alxia/client';\nimport { apiKey, app } from './app';\n\nconst json = { 'content-type': 'application/json', 'x-api-key': apiKey };\n\ntest('creates a todo from JSON', async () => {\n\tconst response = await app.request('/todos', {\n\t\tmethod: 'POST',\n\t\theaders: json,\n\t\tbody: JSON.stringify({ title: 'Write a route' }),\n\t});\n\texpect(response.status).toBe(201);\n\texpect(await response.json()).toEqual({\n\t\tid: expect.any(Number),\n\t\ttitle: 'Write a route',\n\t\tdone: false,\n\t});\n});\n\ntest('refuses an empty title with a 400 naming it', async () => {\n\tconst response = await app.request('/todos', {\n\t\tmethod: 'POST',\n\t\theaders: json,\n\t\tbody: JSON.stringify({ title: '' }),\n\t});\n\texpect(response.status).toBe(400);\n\texpect((await response.json()).issues[0].path).toEqual(['title']);\n});\n\ntest('the typed client reads each status the route answers', async () => {\n\t// Given the app itself, the client calls its fetch in process.\n\tconst api = client(app, { headers: { 'x-api-key': apiKey } });\n\tconst created = await api.post('/todos', { body: { title: 'Call it typed' } });\n\tif (created.status !== 201) throw new Error(\\`got \\${created.status}\\`);\n\texpect(created.data.title).toBe('Call it typed'); // data is the Todo schema's type\n\n\tconst anonymous = await client(app).post('/todos', { body: { title: 'No key' } });\n\texpect(anonymous.status).toBe(401);\n\tif (anonymous.status === 401) expect(anonymous.data.error).toBe('unauthorized');\n});\n`;\n\nconst TSCONFIG = `{\n\t\"compilerOptions\": {\n\t\t\"types\": [\"bun\"],\n\t\t\"lib\": [\"ESNext\", \"DOM\"],\n\t\t\"target\": \"ESNext\",\n\t\t\"module\": \"ESNext\",\n\t\t\"moduleDetection\": \"force\",\n\t\t\"moduleResolution\": \"bundler\",\n\t\t\"verbatimModuleSyntax\": true,\n\t\t\"noEmit\": true,\n\t\t\"skipLibCheck\": true,\n\t\t\"resolveJsonModule\": true,\n\t\t\"strict\": true,\n\t\t\"noImplicitOverride\": true,\n\t\t\"noImplicitReturns\": true,\n\t\t\"noFallthroughCasesInSwitch\": true,\n\t\t\"noUncheckedIndexedAccess\": true,\n\t\t\"exactOptionalPropertyTypes\": true,\n\t\t\"noPropertyAccessFromIndexSignature\": true,\n\t\t\"noUnusedLocals\": true,\n\t\t\"noUnusedParameters\": true,\n\t\t\"forceConsistentCasingInFileNames\": true\n\t},\n\t\"include\": [\"src\"]\n}\n`;\n\nconst GITIGNORE = `node_modules\ndist\n*.log\n.DS_Store\n.env\n.env.local\n`;\n\nconst readme = (name: string) => `# ${name}\n\nAn [alxia](https://github.com/softistx/alxia) app with\n[Zod](https://zod.dev), made with \\`bun create @alxia\\`.\n\n\\`\\`\\`sh\nbun dev # http://localhost:3000, restarted on every change\nbun test # src/app.spec.ts: in process, and through the typed client\nbun run typecheck\nbun run build # dist/server.js, run with \\`bun start\\`\n\\`\\`\\`\n\n\\`\\`\\`sh\ncurl -X POST localhost:3000/todos \\\\\n -H 'content-type: application/json' -H 'x-api-key: dev-key' \\\\\n -d '{\"title\":\"Write a route\"}'\n\\`\\`\\`\n\n- \\`src/app.ts\\`: the app. \\`POST /todos\\` validates its body with Zod, and\n its own hook, \\`requireKey\\`, answers 401 without the \\`x-api-key\\` header.\n Set \\`API_KEY\\` in the environment outside development.\n- \\`src/server.ts\\`: listens on \\`PORT\\`, 3000 by default.\n- \\`src/app.spec.ts\\`: \\`app.request()\\` and \\`@alxia/client\\`, no port.\n\nNext: [Getting started](https://github.com/softistx/alxia/blob/develop/packages/core/docs/guide/getting-started.md)\nand the rest of [\\`@alxia/core\\`'s guide](https://github.com/softistx/alxia/tree/develop/packages/core/docs).\n`;\n\n/** The template's files but its manifest, by path. */\nexport function apiFiles(name: string): Record<string, string> {\n\treturn {\n\t\t'src/app.ts': APP,\n\t\t'src/server.ts': SERVER,\n\t\t'src/app.spec.ts': SPEC,\n\t\t'tsconfig.json': TSCONFIG,\n\t\t'.gitignore': GITIGNORE,\n\t\t'README.md': readme(name),\n\t};\n}\n",
|
|
13
|
+
"/** Where `create-alxia` prints, asks and runs: the process's own, or a spec's. */\n\nexport interface Io {\n\treadonly out: (line: string) => void;\n\treadonly err: (line: string) => void;\n\t/**\n\t * An answer to `question`, `fallback` when empty; undefined when there is\n\t * no terminal to ask in, null when the input ended (Ctrl-D) unanswered.\n\t */\n\treadonly ask: (\n\t\tquestion: string,\n\t\tfallback: string,\n\t) => string | null | undefined;\n\t/** Runs a command in `cwd` with the terminal's streams; its exit code. */\n\treadonly run: (command: readonly string[], cwd: string) => Promise<number>;\n\treadonly env: Record<string, string | undefined>;\n\t/** The registry's `fetch`, for a spec's fake one. */\n\treadonly fetch?: (request: Request) => Promise<Response>;\n}\n\n/** The process's own `Io`: its streams, `prompt()`, `Bun.spawn`. */\nexport function processIo(): Io {\n\treturn {\n\t\tout: (line) => console.log(line),\n\t\terr: (line) => console.error(line),\n\t\task: (question, fallback) => {\n\t\t\tif (!process.stdin.isTTY) return undefined;\n\t\t\t// Bun's prompt() prints the default as `[fallback]`, gives it for an\n\t\t\t// empty line, and null at the end of the input.\n\t\t\tconst answer = prompt(question, fallback);\n\t\t\treturn answer === null ? null : answer.trim() || fallback;\n\t\t},\n\t\trun: async (command, cwd) =>\n\t\t\tBun.spawn([...command], { cwd, stdio: ['inherit', 'inherit', 'inherit'] })\n\t\t\t\t.exited,\n\t\tenv: process.env,\n\t};\n}\n",
|
|
14
|
+
"#!/usr/bin/env bun\n/**\n * `create-alxia`, the package's bin: `bun create @alxia` runs it, as do\n * `bunx @alxia/create` and `npm create @alxia`.\n */\nimport { main, processIo } from './main';\n\nprocess.exitCode = await main(\n\tprocess.argv.slice(2),\n\tprocess.cwd(),\n\tprocessIo(),\n);\n"
|
|
15
|
+
],
|
|
16
|
+
"mappings": "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",
|
|
17
|
+
"debugId": "8B48AE430B9AACCF64756E2164756E21",
|
|
18
|
+
"names": []
|
|
19
|
+
}
|
package/dist/io.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** Where `create-alxia` prints, asks and runs: the process's own, or a spec's. */
|
|
2
|
+
export interface Io {
|
|
3
|
+
readonly out: (line: string) => void;
|
|
4
|
+
readonly err: (line: string) => void;
|
|
5
|
+
/**
|
|
6
|
+
* An answer to `question`, `fallback` when empty; undefined when there is
|
|
7
|
+
* no terminal to ask in, null when the input ended (Ctrl-D) unanswered.
|
|
8
|
+
*/
|
|
9
|
+
readonly ask: (question: string, fallback: string) => string | null | undefined;
|
|
10
|
+
/** Runs a command in `cwd` with the terminal's streams; its exit code. */
|
|
11
|
+
readonly run: (command: readonly string[], cwd: string) => Promise<number>;
|
|
12
|
+
readonly env: Record<string, string | undefined>;
|
|
13
|
+
/** The registry's `fetch`, for a spec's fake one. */
|
|
14
|
+
readonly fetch?: (request: Request) => Promise<Response>;
|
|
15
|
+
}
|
|
16
|
+
/** The process's own `Io`: its streams, `prompt()`, `Bun.spawn`. */
|
|
17
|
+
export declare function processIo(): Io;
|
|
18
|
+
//# sourceMappingURL=io.d.ts.map
|
package/dist/io.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"io.d.ts","sourceRoot":"","sources":["../src/io.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAElF,MAAM,WAAW,EAAE;IAClB,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC;;;OAGG;IACH,QAAQ,CAAC,GAAG,EAAE,CACb,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,KACZ,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAC/B,0EAA0E;IAC1E,QAAQ,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC3E,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IACjD,qDAAqD;IACrD,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;CACzD;AAED,oEAAoE;AACpE,wBAAgB,SAAS,IAAI,EAAE,CAgB9B"}
|
package/dist/main.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Io } from './io';
|
|
2
|
+
export { NAME } from './args';
|
|
3
|
+
export type { Io } from './io';
|
|
4
|
+
export { processIo } from './io';
|
|
5
|
+
export { packageName, refusal } from './target';
|
|
6
|
+
export declare const USAGE = "Usage: bun create @alxia [dir] [options]\n\nWrites a new alxia app in dir, which must be empty or not exist yet. Asks\nfor what is not given.\n\nOptions:\n -t, --template <name> api an alxia app with Zod, a spec and the typed client\n react-router React Router's official template, served by alxia\n --no-install write the files, skip bun install\n -h, --help show this help";
|
|
7
|
+
/** Runs the command in `cwd`; resolves to the process's exit code. */
|
|
8
|
+
export declare function main(args: readonly string[], cwd: string, io: Io): Promise<number>;
|
|
9
|
+
//# sourceMappingURL=main.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,MAAM,CAAC;AAM/B,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,YAAY,EAAE,EAAE,EAAE,MAAM,MAAM,CAAC;AAC/B,OAAO,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AACjC,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAEhD,eAAO,MAAM,KAAK,wbASsB,CAAC;AAEzC,sEAAsE;AACtE,wBAAsB,IAAI,CACzB,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,GAAG,EAAE,MAAM,EACX,EAAE,EAAE,EAAE,GACJ,OAAO,CAAC,MAAM,CAAC,CAgEjB"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/** The dependency fields of a manifest, as a template writes them. */
|
|
2
|
+
export interface Manifest {
|
|
3
|
+
dependencies?: Record<string, string>;
|
|
4
|
+
devDependencies?: Record<string, string>;
|
|
5
|
+
[key: string]: unknown;
|
|
6
|
+
}
|
|
7
|
+
/** How to reach the registry: its URL, and the `fetch` to call it with. */
|
|
8
|
+
export interface Registry {
|
|
9
|
+
readonly url: string;
|
|
10
|
+
readonly fetch?: (request: Request) => Promise<Response>;
|
|
11
|
+
/** How long one package's metadata may take, in milliseconds. */
|
|
12
|
+
readonly timeout?: number;
|
|
13
|
+
}
|
|
14
|
+
/** What a bump changed, and what it could not. */
|
|
15
|
+
export interface Bumped {
|
|
16
|
+
/** One line per dependency moved: `vite ^8.0.3 -> ^8.3.2`. */
|
|
17
|
+
readonly moved: string[];
|
|
18
|
+
/** A newer major that was left out, because a range refuses it. */
|
|
19
|
+
readonly held: string[];
|
|
20
|
+
/** A package whose metadata did not arrive: its version is unchanged. */
|
|
21
|
+
readonly failed: string[];
|
|
22
|
+
/** A package with no release within its range: its version is unchanged. */
|
|
23
|
+
readonly unmatched: string[];
|
|
24
|
+
}
|
|
25
|
+
/** The registry to resolve against: the one Bun or npm was given, or npm's. */
|
|
26
|
+
export declare function registryUrl(env: Record<string, string | undefined>): string;
|
|
27
|
+
/**
|
|
28
|
+
* The range alxia's peers hold `name` to — React Router's packages follow
|
|
29
|
+
* `react-router`'s — or undefined when none does, and npm's `latest` is
|
|
30
|
+
* taken.
|
|
31
|
+
*/
|
|
32
|
+
export declare function allowedRange(name: string): string | undefined;
|
|
33
|
+
/** The newest release (not a prerelease) of `versions` within `range`. */
|
|
34
|
+
export declare function newestWithin(versions: readonly string[], range: string): string | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* Rewrites every dependency of `manifest` but those in `keep` to `^` its
|
|
37
|
+
* newest version: within the range alxia's peers hold it to, or npm's
|
|
38
|
+
* `latest` when none does. React Router's own packages take the
|
|
39
|
+
* version `react-router` resolved to, when they have it: `@react-router/node`
|
|
40
|
+
* pins `react-router` exactly. A package whose metadata fails to arrive keeps
|
|
41
|
+
* its version, and is named in `failed`.
|
|
42
|
+
*/
|
|
43
|
+
export declare function bumpDependencies(manifest: Manifest, registry: Registry, keep?: ReadonlySet<string>): Promise<Bumped>;
|
|
44
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAOA,sEAAsE;AACtE,MAAM,WAAW,QAAQ;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACvB;AAED,2EAA2E;AAC3E,MAAM,WAAW,QAAQ;IACxB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IACzD,iEAAiE;IACjE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,kDAAkD;AAClD,MAAM,WAAW,MAAM;IACtB,8DAA8D;IAC9D,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC;IACzB,mEAAmE;IACnE,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC;IACxB,yEAAyE;IACzE,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC;IAC1B,4EAA4E;IAC5E,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,CAAC;CAC7B;AAED,+EAA+E;AAC/E,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GAAG,MAAM,CAM3E;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAO7D;AAOD,0EAA0E;AAC1E,wBAAgB,YAAY,CAC3B,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,KAAK,EAAE,MAAM,GACX,MAAM,GAAG,SAAS,CAKpB;AA0BD;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CACrC,QAAQ,EAAE,QAAQ,EAClB,QAAQ,EAAE,QAAQ,EAClB,IAAI,GAAE,WAAW,CAAC,MAAM,CAAa,GACnC,OAAO,CAAC,MAAM,CAAC,CAiEjB"}
|
package/dist/target.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** A directory name as a package name: lowercased, `-` for the rest. */
|
|
2
|
+
export declare function packageName(dir: string): string;
|
|
3
|
+
/** Why `target` cannot take a new project, or undefined when it can. */
|
|
4
|
+
export declare function refusal(target: string, shown?: string): Promise<string | undefined>;
|
|
5
|
+
/** Removes what was written: the directory, or what is in the one that was there. */
|
|
6
|
+
export declare function clean(target: string, existed: boolean): Promise<void>;
|
|
7
|
+
//# sourceMappingURL=target.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"target.d.ts","sourceRoot":"","sources":["../src/target.ts"],"names":[],"mappings":"AAIA,wEAAwE;AACxE,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAM/C;AAED,wEAAwE;AACxE,wBAAsB,OAAO,CAC5B,MAAM,EAAE,MAAM,EACd,KAAK,GAAE,MAAe,GACpB,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAU7B;AAED,qFAAqF;AACrF,wBAAsB,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAQ3E"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `api` template: an alxia app with Zod, one validated route behind a
|
|
3
|
+
* hook of its own, a spec that calls it in process and through the typed
|
|
4
|
+
* client, and the scripts to run, test, typecheck and build it.
|
|
5
|
+
*/
|
|
6
|
+
import type { Manifest } from '../registry';
|
|
7
|
+
import type { AlxiaPackage } from '../versions';
|
|
8
|
+
/**
|
|
9
|
+
* The template's manifest. The versions of `zod`, `typescript` and
|
|
10
|
+
* `@types/bun` are where it starts: `bumpDependencies` moves each to the
|
|
11
|
+
* registry's newest within alxia's peer ranges.
|
|
12
|
+
*/
|
|
13
|
+
export declare function apiManifest(name: string, alxia: Record<AlxiaPackage, string>): Manifest;
|
|
14
|
+
/** The template's files but its manifest, by path. */
|
|
15
|
+
export declare function apiFiles(name: string): Record<string, string>;
|
|
16
|
+
//# sourceMappingURL=api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/templates/api.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD;;;;GAIG;AACH,wBAAgB,WAAW,CAC1B,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,GACjC,QAAQ,CAsBV;AAqJD,sDAAsD;AACtD,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAS7D"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Manifest } from '../registry';
|
|
2
|
+
import { type AlxiaPackage } from '../versions';
|
|
3
|
+
/**
|
|
4
|
+
* The scaffold's command line: the newest `create-react-router` of the
|
|
5
|
+
* highest React Router major `@alxia/react-router` accepts, with no prompt,
|
|
6
|
+
* no install and no git repository — the install is ours, once alxia is in.
|
|
7
|
+
*/
|
|
8
|
+
export declare function scaffoldCommand(dir: string): string[];
|
|
9
|
+
/** The scaffold is not what the edits expect: nothing more is written, and `main` removes it. */
|
|
10
|
+
export declare class ScaffoldChanged extends Error {
|
|
11
|
+
constructor(file: string, expected: string);
|
|
12
|
+
}
|
|
13
|
+
export declare const BUNFIG = "[run]\n# The React Router CLI is a node script: this starts it on Bun, which\n# alxia's server needs, even where a node is installed.\nbun = true\n";
|
|
14
|
+
/** `vite.config.ts` with `alxia()` after `reactRouter()`. */
|
|
15
|
+
export declare function addPlugin(config: string): string;
|
|
16
|
+
/** The manifest with alxia's dependencies and `start` on Bun. */
|
|
17
|
+
export declare function addAlxia(manifest: Manifest, alxia: Record<AlxiaPackage, string>): Manifest;
|
|
18
|
+
/**
|
|
19
|
+
* Reads what the scaffold wrote in `dir` and checks it, without writing:
|
|
20
|
+
* the manifest with alxia in, and the files to write beside it.
|
|
21
|
+
*/
|
|
22
|
+
export declare function alxiaLayer(dir: string, alxia: Record<AlxiaPackage, string>): Promise<{
|
|
23
|
+
manifest: Manifest;
|
|
24
|
+
files: Record<string, string>;
|
|
25
|
+
}>;
|
|
26
|
+
//# sourceMappingURL=react-router.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"react-router.d.ts","sourceRoot":"","sources":["../../src/templates/react-router.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,KAAK,YAAY,EAA6B,MAAM,aAAa,CAAC;AAE3E;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAUrD;AAED,iGAAiG;AACjG,qBAAa,eAAgB,SAAQ,KAAK;gBAC7B,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;CAO1C;AAED,eAAO,MAAM,MAAM,wJAIlB,CAAC;AAIF,6DAA6D;AAC7D,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAmBhD;AAED,iEAAiE;AACjE,wBAAgB,QAAQ,CACvB,QAAQ,EAAE,QAAQ,EAClB,KAAK,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,GACjC,QAAQ,CAuBV;AAQD;;;GAGG;AACH,wBAAsB,UAAU,CAC/B,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,GACjC,OAAO,CAAC;IAAE,QAAQ,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,CAAC,CAiBhE"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** The `@alxia/*` packages a template depends on. */
|
|
2
|
+
export type AlxiaPackage = '@alxia/client' | '@alxia/core' | '@alxia/react-router';
|
|
3
|
+
/**
|
|
4
|
+
* The peer ranges of alxia's packages that a template's other dependencies
|
|
5
|
+
* must stay within, by the package they constrain: `typescript` is every
|
|
6
|
+
* package's, `zod` is `@alxia/zod`'s, `react-router` and `vite` are
|
|
7
|
+
* `@alxia/react-router`'s. `versions.spec.ts` checks each against the
|
|
8
|
+
* package that declares it, so a widened peer fails there until it is
|
|
9
|
+
* widened here too.
|
|
10
|
+
*/
|
|
11
|
+
export declare const PEER_RANGES: {
|
|
12
|
+
readonly typescript: "^6.0.3 || ^7.0.0";
|
|
13
|
+
readonly zod: "^4.2.0";
|
|
14
|
+
readonly 'react-router': "^8.0.0";
|
|
15
|
+
readonly vite: "^7.0.0 || ^8.0.0";
|
|
16
|
+
};
|
|
17
|
+
/** The highest major a range such as `^7.0.0 || ^8.0.0` names. */
|
|
18
|
+
export declare function highestMajor(range: string): number;
|
|
19
|
+
/**
|
|
20
|
+
* The range a generated project declares for each `@alxia/*` package: what
|
|
21
|
+
* this package's own `package.json` says. Published, that is `^<version>`,
|
|
22
|
+
* which `bun publish` writes in place of `workspace:^` from the version
|
|
23
|
+
* beside it in the workspace. Inside the workspace, `workspace:^` is
|
|
24
|
+
* resolved the same way, from the sibling's installed `package.json`.
|
|
25
|
+
*/
|
|
26
|
+
export declare function alxiaRanges(manifestUrl?: URL): Promise<Record<AlxiaPackage, string>>;
|
|
27
|
+
//# sourceMappingURL=versions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"versions.d.ts","sourceRoot":"","sources":["../src/versions.ts"],"names":[],"mappings":"AAQA,qDAAqD;AACrD,MAAM,MAAM,YAAY,GACrB,eAAe,GACf,aAAa,GACb,qBAAqB,CAAC;AAQzB;;;;;;;GAOG;AACH,eAAO,MAAM,WAAW;;;;;CAKd,CAAC;AAEX,kEAAkE;AAClE,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAMlD;AAED;;;;;;GAMG;AACH,wBAAsB,WAAW,CAChC,WAAW,GAAE,GAAiD,GAC5D,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC,CAcvC"}
|
package/dist/write.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type Template } from './args';
|
|
2
|
+
import type { Io } from './io';
|
|
3
|
+
import type { AlxiaPackage } from './versions';
|
|
4
|
+
/** Writes the project into `target`; throws on a scaffold that changed. */
|
|
5
|
+
export declare function write(target: string, template: Template, alxia: Record<AlxiaPackage, string>, io: Io): Promise<true>;
|
|
6
|
+
//# sourceMappingURL=write.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"write.d.ts","sourceRoot":"","sources":["../src/write.ts"],"names":[],"mappings":"AAGA,OAAO,EAAQ,KAAK,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAC7C,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,MAAM,CAAC;AAK/B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,2EAA2E;AAC3E,wBAAsB,KAAK,CAC1B,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,QAAQ,EAClB,KAAK,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,EACnC,EAAE,EAAE,EAAE,GACJ,OAAO,CAAC,IAAI,CAAC,CAsCf"}
|
package/docs/README.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# @alxia/create documentation
|
|
2
|
+
|
|
3
|
+
The [package README](../README.md) is the short version. This folder is the
|
|
4
|
+
long one: what each template writes, what the `react-router` template
|
|
5
|
+
changes in React Router's own, how the versions are chosen, and the errors
|
|
6
|
+
the command prints.
|
|
7
|
+
|
|
8
|
+
| Page | Read it when |
|
|
9
|
+
| --- | --- |
|
|
10
|
+
| [Guide](guide.md) | choosing a template, reading what it wrote, pinning or moving versions, or running the command in a script or CI |
|
|
11
|
+
| [Troubleshooting](troubleshooting.md) | the command refused a directory, a template or an option, `create-react-router` changed, the registry did not answer, or `bun install` failed |
|
|
12
|
+
| [Roadmap](roadmap.md) | wondering what is coming, and what is not planned |
|
package/docs/guide.md
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
# Guide
|
|
2
|
+
|
|
3
|
+
How `bun create @alxia` asks, what each template writes, and how it
|
|
4
|
+
chooses the versions it writes.
|
|
5
|
+
|
|
6
|
+
- [Running it](#running-it)
|
|
7
|
+
- [The `api` template](#the-api-template)
|
|
8
|
+
- [The `react-router` template](#the-react-router-template)
|
|
9
|
+
- [Versions](#versions)
|
|
10
|
+
- [In a script or CI](#in-a-script-or-ci)
|
|
11
|
+
|
|
12
|
+
## Running it
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
bun create @alxia
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
With no arguments it asks two questions, each with a default an empty
|
|
19
|
+
answer takes:
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
Where should the project go? [alxia-app] my-app
|
|
23
|
+
Which template? api or react-router [api]
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
then writes the project, runs `bun install` in it, and prints what to run
|
|
27
|
+
next:
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
Done: my-app holds the api template. Next:
|
|
31
|
+
|
|
32
|
+
cd my-app
|
|
33
|
+
bun dev
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
| option | default | |
|
|
37
|
+
| --- | --- | --- |
|
|
38
|
+
| `[dir]` | asked, `alxia-app` | where to write: empty, or not there yet |
|
|
39
|
+
| `--template <name>`, `--template=<name>`, `-t <name>` | asked, `api` | `api` or `react-router` |
|
|
40
|
+
| `--no-install` | install | write the files, skip `bun install` |
|
|
41
|
+
| `--help`, `-h` | | the usage, and nothing else |
|
|
42
|
+
|
|
43
|
+
What the command line gives is not asked: `bun create @alxia my-app
|
|
44
|
+
--template react-router` asks nothing. The directory must be empty or not
|
|
45
|
+
exist yet; `.` writes into the current one, when it is empty, and the next
|
|
46
|
+
steps then start at `bun dev`. The package name in `package.json` is the
|
|
47
|
+
directory's name, lowercased, with `-` for anything npm refuses.
|
|
48
|
+
|
|
49
|
+
`bun create @alxia` is `bunx @alxia/create`: Bun maps `bun create @scope`
|
|
50
|
+
to the package `@scope/create`, and npm maps `npm create @scope` the same
|
|
51
|
+
way. Any of the three runs the same bin, on Bun.
|
|
52
|
+
|
|
53
|
+
## The `api` template
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
my-api/
|
|
57
|
+
├── src/
|
|
58
|
+
│ ├── app.ts the app, and its type
|
|
59
|
+
│ ├── app.spec.ts bun test: app.request() and @alxia/client
|
|
60
|
+
│ └── server.ts app.listen(PORT)
|
|
61
|
+
├── package.json
|
|
62
|
+
├── tsconfig.json
|
|
63
|
+
├── .gitignore
|
|
64
|
+
└── README.md
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
`src/app.ts` is one route, `POST /todos`, with what a real one needs: a
|
|
68
|
+
body validated by a Zod schema, a declared reply, and a hook of its own.
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
import { alxia, defineHook } from '@alxia/core';
|
|
72
|
+
import { z } from 'zod';
|
|
73
|
+
|
|
74
|
+
const Todo = z.object({ id: z.number(), title: z.string(), done: z.boolean() });
|
|
75
|
+
const NewTodo = z.object({ title: z.string().min(1) });
|
|
76
|
+
|
|
77
|
+
/** Set API_KEY in the environment: this default is for development. */
|
|
78
|
+
export const apiKey = Bun.env['API_KEY'] ?? 'dev-key';
|
|
79
|
+
|
|
80
|
+
const requireKey = defineHook(({ request, reply }) =>
|
|
81
|
+
request.headers.get('x-api-key') === apiKey
|
|
82
|
+
? undefined
|
|
83
|
+
: reply(401, { error: 'unauthorized' as const }),
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
const todos: z.infer<typeof Todo>[] = [];
|
|
87
|
+
|
|
88
|
+
export const app = alxia()
|
|
89
|
+
.decorate({ todos })
|
|
90
|
+
.post(
|
|
91
|
+
'/todos',
|
|
92
|
+
[requireKey],
|
|
93
|
+
{ body: NewTodo, response: { 201: Todo } },
|
|
94
|
+
({ body, todos, reply }) => {
|
|
95
|
+
const todo = { id: todos.length + 1, title: body.title, done: false };
|
|
96
|
+
todos.push(todo);
|
|
97
|
+
return reply.created(todo);
|
|
98
|
+
},
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
export type App = typeof app;
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
- `requireKey` runs before the body is read: a request without the key is a
|
|
105
|
+
401 whatever its body, and that 401 joins the route's type
|
|
106
|
+
([Hooks on one route](https://github.com/softistx/alxia/blob/develop/packages/core/docs/guide/hooks.md#hooks-on-one-route)).
|
|
107
|
+
- An empty `title` is a 400 naming `title`, before the handler runs.
|
|
108
|
+
- `todos` lives in memory: replace the array with your database, given to
|
|
109
|
+
the routes the same way, by `decorate`.
|
|
110
|
+
|
|
111
|
+
`src/app.spec.ts` calls the app in process, no port: `app.request()` with a
|
|
112
|
+
JSON body, then `@alxia/client` given the app itself, whose result is typed
|
|
113
|
+
by status:
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
const api = client(app, { headers: { 'x-api-key': apiKey } });
|
|
117
|
+
const created = await api.post('/todos', { body: { title: 'Call it typed' } });
|
|
118
|
+
if (created.status !== 201) throw new Error(`got ${created.status}`);
|
|
119
|
+
expect(created.data.title).toBe('Call it typed'); // data is the Todo schema's type
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
The scripts:
|
|
123
|
+
|
|
124
|
+
| script | runs |
|
|
125
|
+
| --- | --- |
|
|
126
|
+
| `bun dev` | `bun --watch src/server.ts`: restarted on every change, on `PORT` or 3000 |
|
|
127
|
+
| `bun test` | the spec |
|
|
128
|
+
| `bun run typecheck` | `tsc --noEmit` |
|
|
129
|
+
| `bun run build` | `bun build src/server.ts --target=bun --outdir=dist`: one file, its dependencies bundled |
|
|
130
|
+
| `bun start` | `bun dist/server.js` |
|
|
131
|
+
|
|
132
|
+
`tsconfig.json` holds the settings alxia's own packages are checked under:
|
|
133
|
+
`strict`, and past it `exactOptionalPropertyTypes`,
|
|
134
|
+
`noUncheckedIndexedAccess`, `noPropertyAccessFromIndexSignature`,
|
|
135
|
+
`noUnusedLocals` and the rest. Hence `Bun.env['API_KEY']` and not
|
|
136
|
+
`Bun.env.API_KEY`. Loosen what you would rather not keep: alxia's types
|
|
137
|
+
compile under each one, and under none.
|
|
138
|
+
|
|
139
|
+
## The `react-router` template
|
|
140
|
+
|
|
141
|
+
It is React Router's template, not a copy of it: the command runs
|
|
142
|
+
|
|
143
|
+
```sh
|
|
144
|
+
bunx create-react-router@8 my-site --yes --no-install --no-git-init --no-agent-skills --no-motion
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
— the newest `create-react-router` of the React Router major
|
|
148
|
+
`@alxia/react-router` accepts — and then makes the same small change
|
|
149
|
+
[`examples/react-router`](https://github.com/softistx/alxia/tree/develop/examples/react-router)
|
|
150
|
+
made to it, in code:
|
|
151
|
+
|
|
152
|
+
```diff
|
|
153
|
+
// package.json
|
|
154
|
+
"scripts": {
|
|
155
|
+
- "start": "react-router-serve ./build/server/index.js",
|
|
156
|
+
+ "start": "bun build/server/index.js",
|
|
157
|
+
},
|
|
158
|
+
"dependencies": {
|
|
159
|
+
+ "@alxia/core": "^0.3.0",
|
|
160
|
+
+ "@alxia/react-router": "^0.2.0",
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
```diff
|
|
164
|
+
// vite.config.ts
|
|
165
|
+
+import { alxia } from "@alxia/react-router/vite";
|
|
166
|
+
import { reactRouter } from "@react-router/dev/vite";
|
|
167
|
+
…
|
|
168
|
+
- plugins: [tailwindcss(), reactRouter()],
|
|
169
|
+
+ plugins: [tailwindcss(), reactRouter(), alxia()],
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
```toml
|
|
173
|
+
# bunfig.toml, new
|
|
174
|
+
[run]
|
|
175
|
+
# The React Router CLI is a node script: this starts it on Bun, which
|
|
176
|
+
# alxia's server needs, even where a node is installed.
|
|
177
|
+
bun = true
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Every other file is React Router's, as it wrote it. Each edit checks first
|
|
181
|
+
that the file is the one it expects — `reactRouter()` once in `plugins`,
|
|
182
|
+
the `dev`, `build` and `start` scripts, no `bunfig.toml` yet — and when
|
|
183
|
+
React Router's template has changed, the command stops, removes what was
|
|
184
|
+
written, and says which file
|
|
185
|
+
([Troubleshooting](troubleshooting.md#create-alxia-create-react-routers--is-not-what-this-alxiacreate-expects-)).
|
|
186
|
+
|
|
187
|
+
There is no `app/server.ts`: the plugin's default server serves the pages
|
|
188
|
+
in `bun dev` and from the build. To put alxia's hooks or `/api` routes in
|
|
189
|
+
front of the pages, write it out and edit it:
|
|
190
|
+
|
|
191
|
+
```sh
|
|
192
|
+
bunx alxia-react-router reveal
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
The [`@alxia/react-router` guide](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide.md)
|
|
196
|
+
goes from there. React Router's `Dockerfile` is kept as it wrote it, based
|
|
197
|
+
on Node: deploy on Bun as that guide's
|
|
198
|
+
[Deploying](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide.md#deploying)
|
|
199
|
+
says.
|
|
200
|
+
|
|
201
|
+
## Versions
|
|
202
|
+
|
|
203
|
+
A template is written with the versions it was written with, and React
|
|
204
|
+
Router's lags behind its own releases. So before installing, the command
|
|
205
|
+
asks the registry for every dependency's versions, and writes `^` the
|
|
206
|
+
newest one alxia accepts:
|
|
207
|
+
|
|
208
|
+
| dependency | moved to the newest within |
|
|
209
|
+
| --- | --- |
|
|
210
|
+
| `@alxia/core`, `@alxia/client`, `@alxia/react-router` | not moved: the versions this `@alxia/create` was published with |
|
|
211
|
+
| `typescript` | `^6.0.3 \|\| ^7.0.0`, every alxia package's peer range |
|
|
212
|
+
| `zod` | `^4.2.0`, `@alxia/zod`'s |
|
|
213
|
+
| `vite` | `^7.0.0 \|\| ^8.0.0`, `@alxia/react-router`'s |
|
|
214
|
+
| `react-router`, `@react-router/*` | `^8.0.0`, `@alxia/react-router`'s; the `@react-router/*` packages take `react-router`'s version, which `@react-router/node` pins exactly |
|
|
215
|
+
| anything else: `react`, `isbot`, Tailwind, `@types/*` | no alxia range: npm's `latest` |
|
|
216
|
+
|
|
217
|
+
The command prints each move, and each newer major it left out:
|
|
218
|
+
|
|
219
|
+
```
|
|
220
|
+
Resolving the newest versions alxia accepts...
|
|
221
|
+
isbot ^5.1.36 -> ^5.2.2
|
|
222
|
+
@types/node ^22 -> ^26.6.4
|
|
223
|
+
typescript ^5.9.3 -> ^7.0.2
|
|
224
|
+
vite ^8.0.3 -> ^8.3.2
|
|
225
|
+
…
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
```
|
|
229
|
+
typescript: kept to ^6.0.3 || ^7.0.0, where the newest is 7.0.2; npm's latest, 8.0.0, is outside it
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
The registry is the one `BUN_CONFIG_REGISTRY` names, else
|
|
233
|
+
`npm_config_registry` (which `npm create` sets), else
|
|
234
|
+
`https://registry.npmjs.org`. Each package's metadata has five seconds; one
|
|
235
|
+
that does not arrive keeps the template's version, and the command warns
|
|
236
|
+
and goes on.
|
|
237
|
+
|
|
238
|
+
alxia's own packages are not moved, so a project is always written with a
|
|
239
|
+
set released together. `bunx @alxia/create@latest` takes the newest set;
|
|
240
|
+
`bun update` moves an existing project's within its ranges.
|
|
241
|
+
|
|
242
|
+
## In a script or CI
|
|
243
|
+
|
|
244
|
+
Given a directory and a template, the command asks nothing:
|
|
245
|
+
|
|
246
|
+
```sh
|
|
247
|
+
bun create @alxia my-api --template api --no-install
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
With no terminal and something missing, it refuses rather than guess
|
|
251
|
+
([Troubleshooting](troubleshooting.md#create-alxia-no-directory-given-and-no-terminal-to-ask-in)).
|
|
252
|
+
It exits 0 once the project is written and installed, and 1 on any
|
|
253
|
+
refusal or failure.
|
package/docs/roadmap.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Roadmap
|
|
2
|
+
|
|
3
|
+
What `@alxia/create` gives a new app, and what is coming. This page is a
|
|
4
|
+
direction, not a commitment: the version something shipped in is the only
|
|
5
|
+
number on it. Every release, with each change it made, is in
|
|
6
|
+
[`CHANGELOG.md`](https://github.com/softistx/alxia/blob/develop/packages/create/CHANGELOG.md).
|
|
7
|
+
|
|
8
|
+
## Now
|
|
9
|
+
|
|
10
|
+
Nothing scheduled yet.
|
|
11
|
+
|
|
12
|
+
## Next
|
|
13
|
+
|
|
14
|
+
Nothing scheduled yet.
|
|
15
|
+
|
|
16
|
+
## Later
|
|
17
|
+
|
|
18
|
+
- **A Dockerfile on Bun for the `react-router` template.** React Router's
|
|
19
|
+
template ships one based on Node, which no longer runs the app once
|
|
20
|
+
`start` runs Bun; the
|
|
21
|
+
[`@alxia/react-router` guide](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide.md#deploying)
|
|
22
|
+
says what to change. The template keeps React Router's files as they are
|
|
23
|
+
written today, so this waits for a decision to edit more of them.
|
|
24
|
+
|
|
25
|
+
## Not planned
|
|
26
|
+
|
|
27
|
+
- **A copy of React Router's template.** The `react-router` template runs
|
|
28
|
+
React Router's own `create-react-router` and edits what it writes, as
|
|
29
|
+
`examples/react-router` was made: a frozen copy would drift from it.
|
|
30
|
+
- **A runtime dependency.** The prompts are Bun's `prompt()`, the registry
|
|
31
|
+
is read with `fetch`, versions are compared with `Bun.semver`.
|
|
32
|
+
|
|
33
|
+
## Shipped
|
|
34
|
+
|
|
35
|
+
### 0.1.0
|
|
36
|
+
|
|
37
|
+
- `bun create @alxia [dir] [--template api|react-router] [--no-install]`,
|
|
38
|
+
asking for what is not given; the `api` and `react-router` templates;
|
|
39
|
+
dependencies at the newest versions alxia's peer ranges accept.
|