Repository navigation
fix(scaffold): a scaffolded namespace fits the customization-pair cap, so a fresh project from a long name validates with no warning - #29103
Conversation
…imit create-objectstack and `os init` truncated the namespace derived from the project name at 20 characters (the manifest grammar), while a namespace over MAX_CUSTOMIZATION_BASE_NAMESPACE_LENGTH (13) has no paired customization namespace, so a fresh project from a long name warned namespace-no-customization-pair on its first `os validate`. `os init` reads the spec constant; create-objectstack keeps a local copy (the spec is only a devDependency there) pinned equal by a test, and its sanitizeNamespace() moves beside deriveManifestId() so it can be unit tested. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FEkQ5oqaD6uoTrtmva9fde
…epo path check:cross-package-test-inputs reads a repo path spelled in a test as an input of that test; the comment only meant to name the rule. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FEkQ5oqaD6uoTrtmva9fde
📓 Docs Drift CheckThis PR changes 2 package(s): 6 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 5 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 40 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 f1c13113b904ae4202b2c2342cf42a9b35b959ef && git checkout f1c13113b904ae4202b2c2342cf42a9b35b959ef
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 5d3667e292c67642a99c2ce1b02950af9469a82f 51ac52ae0cf24d9060154da888f3d5c6ef6841b8 && git checkout -B drift-repro 5d3667e292c67642a99c2ce1b02950af9469a82f && git merge --no-ff 51ac52ae0cf24d9060154da888f3d5c6ef6841b8
node scripts/docs-audit/affected-docs.mjs --json 5d3667e292c67642a99c2ce1b02950af9469a82f
|
|
domain:server#2 · os-steve · session_01ADHkmQxwoGb5fSnrFkpxmt 席位核对ACCEPT · The maintainer's words for this seat, verbatim: 「接手所有开发,包括跟进之前未完成的开发落地。」
|
|
domain:server#2 · os-steve · session_01ADHkmQxwoGb5fSnrFkpxmt Landing record · PR #29103
Release: ships Generated by Claude Code |
Requested by Leehom · project thread
Fixes #28992
Clause-②: no
Metadata-protocol: none
维护者速读
npx create-objectstack和os init由项目名生成命名空间时,上限从 20 个字符改为 13 个(即MAX_CUSTOMIZATION_BASE_NAMESPACE_LENGTH)。qa-console-first-paint现在得到qa_console_fi,脚手架里的对象名和文件名都跟着它走;截断后不留结尾的_。13 个字符以内的项目名,生成结果与以前逐字节相同。os validate就弹出namespace-no-customization-pair警告,让他去改不是他写的对象名。create-objectstack运行时不依赖 spec,所以保留一份本地常量,由测试钉死等于 spec 的常量;os init直接读 spec 常量。回滚即 revert。packages/create-objectstack/src/rewrite-identity.ts的SCAFFOLD_NAMESPACE_MAX_LENGTH与sanitizeNamespace()(从 index.ts 原样搬来,只改上限);packages/cli/src/commands/init.ts的sanitizeNamespace();packages/create-objectstack/src/scaffold-namespace-cap.test.ts的常量相等钉。pnpm --filter create-objectstack exec vitest run src/scaffold-namespace-cap.test.ts src/scaffold-namespace-customization-pair.test.ts,预期Tests 44 passed (44)。What changes
packages/create-objectstack/src/rewrite-identity.ts:sanitizeNamespace()moves here fromindex.ts, beside its siblingderiveManifestId(), so it can be unit tested (index.tscallsprogram.parse()on import). The only behaviour change is the cap:SCAFFOLD_NAMESPACE_MAX_LENGTH = 13replaces the three20s. The existing trailing-_strip after truncation is kept, so a namespace whose 13th character is_ends one character earlier.MAX_CUSTOMIZATION_BASE_NAMESPACE_LENGTHfrom one place if the scaffolder can depend on it cheaply.@objectstack/specis only a devDependency of create-objectstack. A value import fromsrc/would be bundled into the published scaffolder, andcheck:undeclared-dep-importsrefuses exactly that (a devDependency inlined into published source). Making the spec a runtime dependency would addzodandpg-connection-stringto a two-dependencynpxentry point. Bundle size was not the obstacle: an esbuild probe from spec source gives 3,121 bytes and no zod. So the order's fallback applies: a local constant, pinned equal to the spec constant byscaffold-namespace-cap.test.ts, which resolves@objectstack/spec/kernelto source through the package's existing vitest alias.packages/create-objectstack/src/index.ts: importssanitizeNamespacefrom./rewrite-identity.js. The namespace is still computed once (const namespace = sanitizeNamespace(projectName)) and handed to the manifest, the config and the object-prefix rewrite, so every derived name uses the truncated value.packages/cli/src/commands/init.ts(os init): the same function had the same 20 cap. It now readsMAX_CUSTOMIZATION_BASE_NAMESPACE_LENGTHfrom@objectstack/spec/kernel, a runtime dependency of the CLI already. The seat widened the claim to cover it, so the two scaffolders keep agreeing..changeset/28992-scaffold-namespace-pair-cap.md:create-objectstackpatch and@objectstack/clipatch, with both declaration lines. Nothing newly exported from@objectstack/spec. Thecreate-objectstackindex.tsexport ofsanitizeNamespaceis not in itsexportsmap (only./created-summaryis), so no published surface moved.Tests
scaffold-namespace-customization-pair.test.tsdrives the real CLI throughtsxforqa-console-first-paint. On the unchanged code it failed withExpected: "qa_console_fi",Received: "qa_console_first_pai". Green, it asserts that the namespace isqa_console_fi, thatcustomizationNamespace()(the helper the lint rule and the ensure door use) answers{ ok: true, value: 'qa_console_fi_custom' }, that the config literal matches, and that every scaffolded object name passesvalidateObjectNamespacePrefix.scaffold-namespace-cap.test.ts(43 cases):MAX_CUSTOMIZATION_BASE_NAMESPACE_LENGTH;ManifestSchema.shape.namespace, is not reserved, and has a customization pair;_(abcdefghijkl-mnop→abcdefghijkl) and a digit-leading name (1234567890123456→a123456789012);my-app,crm,abcdefghijklm,thirteen-char,'',x,9-lives,sys,system,@acme/my-app) keep their previous value.test/init.test.ts: five truncation cases and one customization-pair sweep added to itssanitizeNamespaceblock.os validate(red and green), on scaffolds underpackages/cli/node_modules/.probe-28992/, run with the builtpackages/cli/bin/run.js:index.ts(from0be247ce): namespaceqa_console_first_pai,os validateexit 0 with 1namespace-no-customization-pairwarning ("namespace "qa_console_first_pai" is 20 characters …").qa_console_fi,os validateexit 0, 0 occurrences of the rule and 0⚠lines.os init qa-console-first-paint --no-installon this branch:Namespace: qa_console_fi, object fileqa_console_fi_item.object.ts,os validateexit 0 with nonamespace-no-customization-pair. (It prints the pre-existing⚠ No apps or plugins definedfor theapptemplate, which does not depend on the namespace.)scripts/ablation-replace.mjs, anchor hit once each; restore proved by blob == HEAD and an emptygit diff HEAD):SCAFFOLD_NAMESPACE_MAX_LENGTH = 13→20(blob8fb4bd56→3a388a6f):pnpm --filter create-objectstack testreds 15 failed / 330 passed, in the 2 new files only, while the 19 existing files stay green. The red cases are the constant pin, the 8 long names in the pair sweep, the 5 truncation cases, and the real-scaffold case.const max = MAX_CUSTOMIZATION_BASE_NAMESPACE_LENGTH→const max = 20(blobef45f599→a44b2c1f): the five init suites red 5 failed / 118 passed, exactly the 4 new truncation cases and the pair sweep. The exact-fit control stays green.c422fbc2(the follow-up51ac52aechanges one comment line in a test):pnpm --filter create-objectstack test21 files / 345 tests passed, and itstypecheck(which includessrc/**/*.test.ts) passed.pnpm --filter @objectstack/cli typecheckpassed, andvitest run --project unitpassed 368 files / 5,202 tests. The cliintegrationlayer is left to CI: no integration-layer file or spawn entry is touched.Acceptance notes
create-objectstackandos initalready printNamespace: VALUEbefore writing, and the value is now the truncated one. A separate notice would need the untruncated value threaded out ofsanitizeNamespace(), which is more than the order's "only if trivial".docs/audits/2026-09-os-create-os-init-convergence-measurement.mdmentionssanitizeNamespace()in a dated audit table. Its claim (one call site, the config namespace) still holds, so it was left as is.Verification
Head
51ac52ae.node scripts/pm/dispatch-gates.mjs --commands, change set vs merge base0be247ce8): 62 derived. 60 ran green (exit 0). 2 NOT MEASURED, reason: prerequisite, because they read every package's built output and only the CLI closure was built here:check:dual-build-cjs-loadsandcheck:i18n-coverage(exit 3,PREREQUISITE NOT MET). CI runs both. Reconciliation:dispatch-gates --ran→62 derived famil(ies) accounted for — 60 run, 2 NOT-MEASURED.check:cross-package-test-inputsread a repo path spelled in a test comment (packages/lint/src/…) as an undeclared input of the new real-scaffold test. The comment now names the rule by its export (51ac52ae), and the gate passes:OK: 31 package(s) read outside themselves, all declared.check-plugin-teardown-shape.mjs --self-testfirst exited 3 because the shallow clone could not reach its pinned fixture commit. Aftergit fetch --depth=1of that commit it passed (48 cases).test:repowas not run locally (no file of that project is touched). CI's Test Core covers it.Gate record (60 + 2)
Generated by Claude Code