Repository navigation
fix(cli): os doctor reads an unset NODE_ENV as development in the source checkout os dev serves - #22231
Conversation
…rce checkout os dev serves Beside an objectstack.config.ts that boots as itself (alone, or beside its own dist/objectstack.json), the NODE_ENV row is now `ok` and names what os dev and os start do, with no fix text. Everywhere else the #5673 warning and its fix are unchanged. The posture comes from utils/artifact-precedence.ts (resolveArtifactBootSource + cwdConfigJoinsBoot), read over the .env* cascade os dev loads; doctorNodeEnv() and the cascade doctor reports are untouched. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWZbGvPFcRKvUqASZtunCU
📓 Docs Drift CheckThis PR changes 1 package(s): 12 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 4 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 28 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 c51a91ba889fbdf771d6ac397309349eb73f147b && git checkout c51a91ba889fbdf771d6ac397309349eb73f147b
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 7b926f76007316ec13d2b17ec4b0a316b94e4d7e 12e2b1ed6b692fabb06fc4c732e4df1a2a29444b && git checkout -B drift-repro 7b926f76007316ec13d2b17ec4b0a316b94e4d7e && git merge --no-ff 12e2b1ed6b692fabb06fc4c732e4df1a2a29444b
node scripts/docs-audit/affected-docs.mjs --json 7b926f76007316ec13d2b17ec4b0a316b94e4d7e
|
Fixes #22163
Clause-②: no
What changed
os doctor'sNODE_ENVrow now depends on the directory it runs in. WithNODE_ENVunset, it picks one of two rows. Doctor's exit code does not change in either case.os devserves: anobjectstack.config.tsthat boots as itself, either alone or beside its owndist/objectstack.json✓ NODE_ENV Not set — os dev runs this project as development; os start forces production, with no fix textOS_ARTIFACT_URL, a config beside anOS_ARTIFACT_PATHnaming another artifact, an artifact with no config, or an empty directory⚠ … treated as productionwarning and its fix. Both are byte-identical to what they were before this changeA
NODE_ENVthat is set still prints no row in either case.The maintainer's direction, quoted in the card: 「
os doctor在开发项目里警告 "NODE_ENV not set — treated as production"。 刚脚手架完就被告知在跑生产,令人困惑;旁边有objectstack.config.ts且无产物时应按开发语义判断。」packages/cli/src/commands/doctor.ts:nodeEnvCheck(env, opts)gainsopts.sourcePosture, which defaults tofalse. A caller that does not pass it gets the NODE_ENV 未设置时 /discovery 广播 environment=development,而 os start 默认 NODE_ENV=production、CLI doctor 也按 production 解析 #5673 row, so every existing caller and pin compiles and behaves as before.nodeEnvSourcePosture(cwd, env)decides the posture by callingutils/artifact-precedence.ts's ownresolveArtifactBootSourceand thencwdConfigJoinsBoot. It does not copy the precedence order, which that module's header forbids.os doctorin a freshly scaffolded dev project warns "NODE_ENV Not set — this environment is being treated as production"; beside anobjectstack.config.tswith no artifact it should read as development #22163 direction beside NODE_ENV 未设置时 /discovery 广播 environment=development,而 os start 默认 NODE_ENV=production、CLI doctor 也按 production 解析 #5673's argument. I also rewrote therun()call-site comment, which the change made false.doctorNodeEnv()and the.env*cascade doctor resolves. TheEnvironment filesrow still saysnode_env=production./discoverydefault.os serve,os startandos devresolve the mode.serve.ts,start.ts,dev.ts,packages/spec,content/docsordocs/qais touched.The PM readings, measured
os dev. Confirmed.create-objectstack@17.7.0 h1-app --template blank(packages installed), then ran the publishedos doctor.os devruns (os compile --output $PWD/dist/objectstack.json, which wrotedist/objectstack.json, 2.4 KB), and ran doctor again.dist/objectstack.json") would turn this very project back to the production row after its firstos dev. The posture therefore accepts the config's own compiled output at the conventional path.dist/exists:--verbose, no fix line follows theNODE_ENVrow. Exit code 0.artifact-precedence.ts. Held, with one deliberate difference fromos dev.os devreadsOS_ARTIFACT_URL/OS_ARTIFACT_PATHafter a.env*load.dev.ts:330,dotenvFlow.config({ node_env: 'development', silent: true }), runs beforeresolveArtifactBootSourceat:348.Environment filesrow reports does not change.configCompiledTothatdev.ts:366passes. Under a localOS_ARTIFACT_PATH,os devcompiles the config into that path and counts the file as the config's own. With that input,cwdConfigJoinsBootanswerstruefor "a config plus anOS_ARTIFACT_PATHnaming another artifact", which contradicts both the triage direction ("everywhere else NODE_ENV 未设置时 /discovery 广播 environment=development,而 os start 默认 NODE_ENV=production、CLI doctor 也按 production 解析 #5673's production default stands") and pin (c).configCompiledToand nohomeDir. Its table is the PM's six rows exactly.ok, has the card's message, and carries nofix.HealthCheckResult.statusis not widened.nodeEnvCheckandnodeEnvSourcePosturecannot be reached from any@objectstack/clientry export. The builtdist/index.d.tsre-exports onlydefault as DoctorCommandfromcommands/doctor.js.console.d.tsandhook-body.d.tsdo not mention doctor. SoClause-②: nostands.Pins
doctor-node-env-default.test.ts:ok, the message namesos devandos start, the production sentence is absent, andfixis undefined.sourcePosture: false:toEqualthe default call. The default call is the row that the unchanged NODE_ENV 未设置时 /discovery 广播 environment=development,而 os start 默认 NODE_ENV=production、CLI doctor 也按 production 解析 #5673 cases in the same file pin.NODE_ENV: no row in either posture.nodeEnvSourcePosture):dist/.OS_ARTIFACT_PATHnaming another artifact, or besideOS_ARTIFACT_URL;.env.developmentnaming an artifact moves the posture to not-source..env.productiondoes not, becauseos devnever loads it.Doctor.runin a temp cwd, in the same harness as the NODE_ENV 未设置时 /discovery 广播 environment=development,而 os start 默认 NODE_ENV=production、CLI doctor 也按 production 解析 #5673 cases).NODE_ENVis unset in (a) to (d), and doctor exits 0 in all five:✓row namingos devandos start, with notreated as production.dist/objectstack.jsongives the same row.OS_ARTIFACT_PATHnaming another artifact gives the⚠ … treated as productionrow.prints the row when NODE_ENV is unset, kept as it was.NODE_ENV=developmentandNODE_ENV=productionin the source posture: noNODE_ENVrow.import { defineStack } from '@objectstack/spec'), and nothing in the temp cwd can resolve it. Case (a) asserts thatCould not load config for analysisis printed in the same report as the development row.doctor-env-provenance.test.ts. The docblock namesOS_ARTIFACT_URLandOS_ARTIFACT_PATH, and the guard requires everyOS_*name indoctor.tsto be declared inDOCTOR_ENV_INPUTS. I did not declare them there:Environment filesrow attributes those inputs against the node_env=production cascade, but these two are read through the development one. A line for them in that row would name the wrong file, and H4 rules out rewording that row.POSTURE_ONLYallowance with that reason. A second assertion requires both names to still appear indoctor.ts, so the allowance cannot outlive its reader.Ablation (one-time proof, not kept)
12e2b1edfirst.node scripts/ablation-replace.mjsreplaced the call-site anchornodeEnvCheck(process.env, { sourcePosture: nodeEnvSourcePosture(cwd) })withsourcePosture: false:e38feeb4a35e→f875089c53b6.dist/was involved: the test imports./doctor.js, which resolves tosrc.× (a) … expected ' ⚠ NODE_ENV Not set — th…' to contain '✓'× (b) … expected ' ⚠ NODE_ENV …' to contain 'os dev'Tests 2 failed | 21 passed (23)e38feeb4a35eequals the blob at HEAD, andgit diff HEADis empty.Byte identity outside the source posture
doctor --verboseprinted theNODE_ENVrow and its 11 fix lines byte-identical (cmp) from this branch's CLI and from the published 17.7.0 CLI.doctor.tsdiff removes no line of the warning's literal.Verification
All of the following ran on HEAD
12e2b1ed.pnpm turbo run build --filter=@objectstack/cli... --concurrency=2: 59 tasks, 57 cached, exit 0.vitest run --project uniton the two pin files:Test Files 2 passed (2),Tests 47 passed (47).@objectstack/cliunit tier.Test Files 1 failed | 262 passed (263),Tests 1 failed | 3886 passed (3887).test/hook-timeout-override-refusal.test.ts › ⭐ CONTROL — a run naming no override passes and says nothing, withTest timed out in 5000ms. That file does not touch doctor.Test Files 1 passed (1),Tests 4 passed (4).@objectstack/clitypecheck: exit 0.tsc --noEmit --listFilescounts both pin files in the program.check:test-typecheck: OK.dispatch-gates.mjson12e2b1edline for line. All 64 exited 0.check:dual-build-cjs-loadsandcheck:i18n-coveragefirst exited 3 (PREREQUISITE NOT MET: packages unbuilt in this worktree). After a fullturbo run build(72 tasks, 71 cached) both exited 0:✓ check:dual-build-cjs-loads — 106 published require entry point(s) across 66 package(s) loadcheck-i18n-coverage: OK (13 config(s), 621 baselined untranslated string(s), none new)dispatch-gates --ran:64 derived famil(ies) accounted for — 64 run, 0 NOT-MEASURED.pnpm lint(eslint . --no-inline-config): exit 0, with no findings printed.Acceptance notes
Environment filesreports the cascade doctor resolved, which is the oneos serve/os startload withNODE_ENVunset. TheNODE_ENVrow now says whatos devdoes. A newcomer still readsnode_env=productionone line above "runs this project as development", so a milder form of this card's confusion remains on that row.OS_*inputs against the production cascade, whileos devloads the development one. That is the boundary the PM ruling onos doctor不加载.env*,读到的环境与serve/dev/start实际运行的不是同一份 —— 写在.env里的配置错误 doctor 一律看不见 #5387 drew when it declined a--devmode for doctor. It is not changed here. Following up is thedomain:cliseat's call. Carrier:domain:cliseat.OS_ARTIFACT_PATHset to the conventional path.OS_ARTIFACT_PATH=./dist/objectstack.jsonbeside a config counts as the source posture. That is the shared predicate's answer (the artifact is the config's own compiled output), not a separate rule here.Generated by Claude Code