Repository navigation
fix(create-objectstack): refuse a project name npm would refuse, before writing anything - #26796
Conversation
…re writing anything The resolved project name (argument or current directory basename) is checked with validate-npm-package-name's validForNewPackages before the first write. A refused name exits 1 with npm's reasons, the rule, and a suggested slug; the name is never rewritten silently. Claude-Session: https://claude.ai/code/session_01QSeKYmQXM3rHJcPnrEPk4B Co-authored-by: Claude <noreply@anthropic.com>
… control Claude-Session: https://claude.ai/code/session_01QSeKYmQXM3rHJcPnrEPk4B Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QSeKYmQXM3rHJcPnrEPk4B Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check10 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. What this run could not see
Coarse fallback — 13 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 5f5435368635f193bbea83850b7ec3eac51ea679 && git checkout 5f5435368635f193bbea83850b7ec3eac51ea679
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 7bdff7ec5a8c0ed80c51f87d0c77ac2ddd7a57d5 0108d0c2038c41fc3644f1006cfc53e87f28dd6d && git checkout -B drift-repro 7bdff7ec5a8c0ed80c51f87d0c77ac2ddd7a57d5 && git merge --no-ff 0108d0c2038c41fc3644f1006cfc53e87f28dd6d
node scripts/docs-audit/affected-docs.mjs --json 7bdff7ec5a8c0ed80c51f87d0c77ac2ddd7a57d5 |
席位核对 · ACCEPT ·
|
Fixes #26642
Clause-②: no
Metadata-protocol: none
维护者速读
create-objectstack在写任何文件之前,先用 npm 自己的包名规则检查项目名(参数,或无参数时的当前目录名);不合法就退出 1,打印 npm 的拒绝理由、规则和一个建议名(My App!与MyApp都建议my-app)。名字永不被悄悄改写。"My App!"原样写进package.json和 manifest,validate、build都通过,直到npm publish才被拒,离原因很远。validate-npm-package-name(npm 自己用的判定模块,零依赖,锁文件里本来就有 7.0.2);以前能"成功"的非法名(包括./my-app这种路径写法)现在会被拒。回滚即 revert 本 PR。packages/create-objectstack/src/index.ts中checkProjectName的接线(必须在第一次写盘之前);②src/package-name.ts(判定与建议名);③package.json与pnpm-lock.yaml的新依赖。node packages/create-objectstack/bin/create-objectstack.js "My App!" --skip-install --skip-skills; echo $?,预期看到规则与Suggested name: create-objectstack my-app,退出码 1,目录里没有任何新文件。What changes
src/package-name.ts.checkProjectName(name)returns npm's own verdict for a NEW package (validate-npm-package-name'svalidForNewPackages), with npm's reasons verbatim.suggestPackageName(name)derives a slug (My App!andMyAppgivemy-app,@Acme/My Appgives@acme/my-app, combining marks are dropped) and returns nothing when the slug is empty or would itself be refused (HTTPslugs to the core module namehttp), so a refusal never recommends a name it would refuse next.describeInvalidProjectNamerenders the refusal: the headline, npm's reasons, the rule in one sentence, and the re-run line.src/index.ts. The check runs on the resolvedprojectName(so the no-argument, current-directory branch is covered too) right after it is resolved and before the first write. A refused name exits 1. In the current-directory case the remedy says the name came from the directory: rename it, or scaffold into a new directory with an explicit name.shadcn), so no new package enters the install graph.@types/validate-npm-package-nameis a dev dependency for the typecheck. The lockfile diff is only the two importer entries:pnpm addalso re-picked an unrelatedjsdompeer in two snapshot keys, and that churn was reverted by hand (pnpm install --frozen-lockfileexits 0 on the result).create-objectstackhas no interactive prompt, so the refusal is the only path. Nothing is rewritten: the written value is always what the user typed.patchchangeset,.changeset/26642-npm-name-check.md.Tests
New
src/package-name.test.ts(32 tests):my-app,@scope/my-app,my.app,a_b,app2,x; refusesMy App!,MyApp,HTTP,http,.hidden,_private, a leading space,node_modules,apps/my-appand a 215-character name, each naming npm's reason;HTTPand!!!, and every slug it does return passesvalidForNewPackages;tsx:My App!andMyAppexit 1, print the rule andSuggested name: create-objectstack my-app, and leave the temp directory empty; a current directory namedMy Appexits 1 and stays empty; control legs:my-appand@scope/my-appexit 0 and write the name verbatim topackage.jsonandobjectstack.manifest.json.Ablation (the refusal pins can fail):
node scripts/ablation-replace.mjsreplacedif (!nameVerdict.valid) {withif (false && !nameVerdict.valid) {inindex.ts(anchor 1 to 0, blob625a2dc9to9824e843), and the three CLI refusal legs went red while the control legs stayed green. The tool then restored the file (blob equal to HEAD,git diff HEADempty).Ablation run (excerpt)
Package build, typecheck, full test suite at 0108d0c
The built
dist/index.jskeepsvalidate-npm-package-nameexternal (import validateNpmPackageName from "validate-npm-package-name"), andnode bin/create-objectstack.js "My App!"exits 1 with the refusal, whilemy-appexits 0 with"name": "my-app".Acceptance notes
create-objectstack ./my-apporapps/my-appused to write that string (an illegal package name) intopackage.json; it is now refused with the slug (my-app,apps-my-app). The[name]argument is documented as a name, not a path.os init(packages/cli/src/commands/init.ts,validateProjectName) already refuses before writing, with a hand-written subset of npm's rules: it refuses scoped names and does not refuse core module names such ashttp, and it suggests no slug. The two scaffolders therefore answer differently for@scope/my-appandhttp. Recorded only; no wrong answer fromos initwas measured here.test:repo(the vitestrepoproject) was not run locally; it belongs to CI'sTest Core.Verification
Gate families derived from the diff (
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, no paths, at0108d0c2): 71 commands. 68 exit 0. 3 are NOT MEASURED (exit 3, PREREQUISITE NOT MET), and CI'sLint & Repo Gatesruns them on a full build:pnpm check:dual-build-cjs-loadsandpnpm check:lean-entry-closureneed every package'sdist, and onlycreate-objectstackand its closure were built;node scripts/check-plugin-teardown-shape.mjs --self-testneeds an unshallowed clone (checker health only).--ranreconciliation with recorded exit codes:71 derived famil(ies) accounted for — 68 run, 3 NOT-MEASURED (3 DERIVED from a recorded exit 3).pnpm exec eslinton the three changed source files exits 0. The control-byte scan of the new files is clean, andcheck:nul-bytesis OK.Selected gate verdict lines
Durable attribution: written by an os-dev agent in session
session_01QSeKYmQXM3rHJcPnrEPk4B, dispatched by the PM loop (seatdomain:server#3).Generated by Claude Code