create-kai 0.1.0 → 0.1.1

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/README.md CHANGED
@@ -240,7 +240,7 @@ not evidence.
240
240
  | `scripts/build.mjs` devDep check | a devDependency range disagreeing with `packages/ui`. `.npmrc` sets `node-linker=hoisted`, so one version wins workspace-wide — an `@types/node: ^22` here downgraded the KIT from 26 and broke its emitted-code suite |
241
241
  | `test/kit-contract.test.ts` | a template importing something the kit does not export |
242
242
  | `scripts/build.mjs` bundle-graph check | the CLI bundle reaching a module it must not. Three symbols imported out of the MCP's `tools/scaffold.ts` took `dist/index.js` from 203 kB to 904 kB, 505 kB of it zod, and **every other check stayed green** — emitted output was byte-identical, `verify:scaffold` was 616/616. It grades the esbuild metafile's real module graph, so it bans the CAUSE (anything under `agent-tooling/mcp/`) and not only the symptom |
243
- | `scripts/verify-pack.mjs` | npm stripping `.gitignore` out of the tarball, and templates missing from `files` |
243
+ | `scripts/verify-pack.mjs` | what only the PACKED tarball can show. npm drops a fixed set of names from every package whatever `files` says, so a template carrying one loses it at publish time and nowhere else: the working tree has it, the build copies it, every test that reads `dist/templates` sees it. `.gitignore` was handled from the start; `.npmrc` was not, and 0.1.0 shipped `nextjs` and `tanstack-start` with none while the bundled CLI still patched one — `ENOENT: ... open '.../myapp/.npmrc'` on the first command a user runs, two of eight frameworks, with this script printing `tarball OK`. It now checks the whole `STRIPPED_DOTFILES` list rather than one name, and asserts every file the patch tables OPEN is in the listing — derived from `PATCHES`/`GATEWAY_PATCHES`, because a hand-written list would have had exactly the hole that produced this. Also: templates missing from `files`, and a template that packs to nothing |
244
244
  | `scripts/smoke.mjs` | an emitted project that installs but does not build. `--framework all` covers every ready framework; without a flag it only ever built React, which meant it answered "does React still build" no matter which framework you had just turned on |
245
245
 
246
246
  ## Publish gate
package/dist/index.js CHANGED
@@ -3457,7 +3457,7 @@ function validateProjectName(name) {
3457
3457
  }
3458
3458
 
3459
3459
  // src/generate.ts
3460
- import { cp, mkdir, readFile, readdir, rename, stat, writeFile } from "node:fs/promises";
3460
+ import { cp, mkdir, mkdtemp, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
3461
3461
  import { existsSync } from "node:fs";
3462
3462
  import path from "node:path";
3463
3463
  import { fileURLToPath } from "node:url";
@@ -3936,8 +3936,15 @@ function patchesFor(templateDir) {
3936
3936
  return PATCHES[templateDir] ?? [];
3937
3937
  }
3938
3938
 
3939
+ // src/template-dotfiles.ts
3940
+ var GITIGNORE_SOURCE_NAME = ".gitignore";
3941
+ var STRIPPED_DOTFILES = [GITIGNORE_SOURCE_NAME, ".npmrc"];
3942
+ function travellingName(dotfile) {
3943
+ return `_${dotfile.slice(1)}`;
3944
+ }
3945
+
3939
3946
  // src/generate.ts
3940
- var GITIGNORE_TEMPLATE_NAME = "_gitignore";
3947
+ var GITIGNORE_TEMPLATE_NAME = travellingName(GITIGNORE_SOURCE_NAME);
3941
3948
  function defaultTemplateRoot() {
3942
3949
  return path.join(path.dirname(fileURLToPath(import.meta.url)), "templates");
3943
3950
  }
@@ -3960,25 +3967,68 @@ async function generate(plan, options = {}) {
3960
3967
  `create-kai: no template for '${framework.id}' at ${templateDir}. Run the package build (it copies examples/starters/* into dist/templates).`
3961
3968
  );
3962
3969
  }
3963
- await mkdir(plan.dir, { recursive: true });
3964
- await cp(templateDir, plan.dir, { recursive: true });
3965
- const underscored = path.join(plan.dir, GITIGNORE_TEMPLATE_NAME);
3966
- if (existsSync(underscored)) {
3967
- await rename(underscored, path.join(plan.dir, ".gitignore"));
3970
+ const parent = path.dirname(plan.dir);
3971
+ await mkdir(parent, { recursive: true });
3972
+ const out = await mkdtemp(path.join(parent, ".create-kai-"));
3973
+ let previousKitSpec;
3974
+ try {
3975
+ await cp(templateDir, out, { recursive: true });
3976
+ for (const dotfile of STRIPPED_DOTFILES) {
3977
+ const travelling = path.join(out, travellingName(dotfile));
3978
+ if (existsSync(travelling)) await rename(travelling, path.join(out, dotfile));
3979
+ }
3980
+ for (const patch of patchesFor(framework.templateDir)) {
3981
+ const file = path.join(out, patch.file);
3982
+ await writeFile(file, applyPatch(patch, await readFile(file, "utf8"), plan.name), "utf8");
3983
+ }
3984
+ const pkgPath = path.join(out, "package.json");
3985
+ const rewritten = rewritePackageJson(await readFile(pkgPath, "utf8"), {
3986
+ name: plan.name,
3987
+ kit: plan.kit,
3988
+ gatewayDeps: integration.deps.npm
3989
+ });
3990
+ previousKitSpec = rewritten.previousKitSpec;
3991
+ await writeFile(pkgPath, stringifyPackageJson(rewritten.json), "utf8");
3992
+ const appSource = await readFile(path.join(out, framework.paths.app), "utf8");
3993
+ const thread = goLiveThread(appSource, framework);
3994
+ const routeFiles = await writeGateway(plan, framework, integration, out, thread);
3995
+ await writeFile(
3996
+ path.join(out, "kai.json"),
3997
+ stringifyKaiJson(buildKaiJson(plan, framework)),
3998
+ "utf8"
3999
+ );
4000
+ await writeFile(
4001
+ path.join(out, "README.md"),
4002
+ plan.gatewayId === "mock" ? renderMockReadme(plan, framework, thread, integration.docsSlug) : renderGatewayReadme(plan, framework, integration, routeFiles[0].path),
4003
+ "utf8"
4004
+ );
4005
+ } catch (error) {
4006
+ await rm(out, { recursive: true, force: true });
4007
+ throw error;
3968
4008
  }
3969
- for (const patch of patchesFor(framework.templateDir)) {
3970
- const file = path.join(plan.dir, patch.file);
3971
- await writeFile(file, applyPatch(patch, await readFile(file, "utf8"), plan.name), "utf8");
4009
+ await commit(out, plan.dir);
4010
+ return {
4011
+ dir: plan.dir,
4012
+ files: await listFiles(plan.dir),
4013
+ previousKitSpec,
4014
+ runNote: integration.runNote,
4015
+ docsSlug: integration.docsSlug
4016
+ };
4017
+ }
4018
+ async function commit(from, to) {
4019
+ try {
4020
+ await rename(from, to);
4021
+ return;
4022
+ } catch (error) {
4023
+ const code = error.code;
4024
+ if (code !== "EEXIST" && code !== "ENOTEMPTY" && code !== "EPERM" && code !== "EXDEV") {
4025
+ throw error;
4026
+ }
3972
4027
  }
3973
- const pkgPath = path.join(plan.dir, "package.json");
3974
- const { json, previousKitSpec } = rewritePackageJson(await readFile(pkgPath, "utf8"), {
3975
- name: plan.name,
3976
- kit: plan.kit,
3977
- gatewayDeps: integration.deps.npm
3978
- });
3979
- await writeFile(pkgPath, stringifyPackageJson(json), "utf8");
3980
- const appSource = await readFile(path.join(plan.dir, framework.paths.app), "utf8");
3981
- const thread = goLiveThread(appSource, framework);
4028
+ await cp(from, to, { recursive: true });
4029
+ await rm(from, { recursive: true, force: true });
4030
+ }
4031
+ async function writeGateway(plan, framework, integration, out, thread) {
3982
4032
  const routeFiles = plan.gatewayId === "mock" ? [] : emitRoute(integration, framework);
3983
4033
  if (plan.gatewayId !== "mock") {
3984
4034
  if (routeFiles.length === 0) {
@@ -3987,12 +4037,12 @@ async function generate(plan, options = {}) {
3987
4037
  );
3988
4038
  }
3989
4039
  for (const file of routeFiles) {
3990
- const abs = path.join(plan.dir, file.path);
4040
+ const abs = path.join(out, file.path);
3991
4041
  await mkdir(path.dirname(abs), { recursive: true });
3992
4042
  await writeFile(abs, file.contents, "utf8");
3993
4043
  }
3994
4044
  for (const patch of gatewayPatchesFor(framework.templateDir)) {
3995
- const file = path.join(plan.dir, patch.file);
4045
+ const file = path.join(out, patch.file);
3996
4046
  await writeFile(
3997
4047
  file,
3998
4048
  applyGatewayPatch(patch, await readFile(file, "utf8"), {
@@ -4006,29 +4056,13 @@ async function generate(plan, options = {}) {
4006
4056
  }
4007
4057
  if (integration.envVars.length > 0) {
4008
4058
  await writeFile(
4009
- path.join(plan.dir, framework.paths.env),
4059
+ path.join(out, framework.paths.env),
4010
4060
  renderEnvFile(integration.envVars, integration.runNote),
4011
4061
  "utf8"
4012
4062
  );
4013
4063
  }
4014
4064
  }
4015
- await writeFile(
4016
- path.join(plan.dir, "kai.json"),
4017
- stringifyKaiJson(buildKaiJson(plan, framework)),
4018
- "utf8"
4019
- );
4020
- await writeFile(
4021
- path.join(plan.dir, "README.md"),
4022
- plan.gatewayId === "mock" ? renderMockReadme(plan, framework, thread, integration.docsSlug) : renderGatewayReadme(plan, framework, integration, routeFiles[0].path),
4023
- "utf8"
4024
- );
4025
- return {
4026
- dir: plan.dir,
4027
- files: await listFiles(plan.dir),
4028
- previousKitSpec,
4029
- runNote: integration.runNote,
4030
- docsSlug: integration.docsSlug
4031
- };
4065
+ return routeFiles;
4032
4066
  }
4033
4067
  function renderEnvFile(envVars, runNote) {
4034
4068
  return [
@@ -4187,7 +4221,7 @@ function detectPackageManager(userAgent = process.env.npm_config_user_agent) {
4187
4221
  }
4188
4222
 
4189
4223
  // src/index.ts
4190
- var DEFAULT_KIT_RANGE = `^${"0.23.0"}`;
4224
+ var DEFAULT_KIT_RANGE = `^${"0.24.0"}`;
4191
4225
  var HELP = `
4192
4226
  ${import_picocolors3.default.bold("create-kai")} \u2014 scaffold a runnable @kitn.ai/ui chat app
4193
4227
 
@@ -4218,7 +4252,7 @@ async function main() {
4218
4252
  return 0;
4219
4253
  }
4220
4254
  if (args.version) {
4221
- console.log("0.1.0");
4255
+ console.log("0.1.1");
4222
4256
  return 0;
4223
4257
  }
4224
4258
  if (args.list) {
@@ -4374,7 +4408,7 @@ function run(command, cwd) {
4374
4408
  }
4375
4409
  function printMatrix(asJson) {
4376
4410
  const matrix = {
4377
- cli: "0.1.0",
4411
+ cli: "0.1.1",
4378
4412
  kit: DEFAULT_KIT_RANGE,
4379
4413
  frameworks: FRAMEWORKS.map((f) => ({
4380
4414
  id: f.id,
@@ -0,0 +1,5 @@
1
+ # Pack the local `@kitn.ai/ui` (file:../../../packages/ui) into a real node_modules copy instead
2
+ # of symlinking it. Next.js/webpack follows symlinks to their realpath, which puts
3
+ # the kit's prebuilt dist/ outside node_modules — Next then transpiles it and trips
4
+ # on the minified Shiki chunk. A packed copy is also what a real npm install yields.
5
+ install-links=true
@@ -0,0 +1,4 @@
1
+ # Pack the local `@kitn.ai/ui` (file:../../../packages/ui) into a real node_modules copy instead
2
+ # of symlinking it, so the example reproduces a real npm install layout (the kit's
3
+ # prebuilt dist/ lives under node_modules, not a live link to repo source).
4
+ install-links=true
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-kai",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "type": "module",
5
5
  "description": "Scaffold a runnable @kitn.ai/ui chat app. `npm create kai@latest`",
6
6
  "license": "MIT",
@@ -26,10 +26,15 @@
26
26
  "name": "create-kai",
27
27
  "targets": {
28
28
  "test": {
29
- "dependsOn": ["build", "^build"]
29
+ "dependsOn": [
30
+ "build",
31
+ "^build"
32
+ ]
30
33
  },
31
34
  "typecheck": {
32
- "dependsOn": ["^build"]
35
+ "dependsOn": [
36
+ "^build"
37
+ ]
33
38
  }
34
39
  }
35
40
  },