@weotro/dx 0.2.1 → 0.3.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.
@@ -0,0 +1,135 @@
1
+ import { accessSync, closeSync, constants, openSync, readFileSync, readSync, readdirSync, statSync } from 'node:fs'
2
+ import { basename, join } from 'node:path'
3
+
4
+ export const standardCommands = ['setup', 'start', 'build', 'test', 'db', 'deploy', 'lint']
5
+ const testTypes = ['unit', 'e2e']
6
+ const databaseActions = ['migrate', 'seed', 'reset', 'status']
7
+ const own = (object, key) => Object.hasOwn(object, key)
8
+ const isObject = value => value !== null && typeof value === 'object' && !Array.isArray(value)
9
+
10
+ export function inspectScript(entry) {
11
+ const name = basename(entry)
12
+ if (!/^[a-z][a-z0-9-]*$/.test(name)) return `入口 ${entry} 名称非法;请使用小写字母开头及小写字母、数字或连字符。`
13
+ if (name === 'check' || name === 'initial') return `入口 ${entry} 使用内置保留名称;请重命名应用脚本。`
14
+ try {
15
+ if (!statSync(entry).isFile()) return `入口 ${entry} 不是普通文件;请提供可执行脚本。`
16
+ accessSync(entry, constants.R_OK | constants.X_OK)
17
+ const descriptor = openSync(entry, 'r')
18
+ try {
19
+ const prefix = Buffer.alloc(256)
20
+ const length = readSync(descriptor, prefix, 0, prefix.length, 0)
21
+ const bytes = prefix.subarray(0, length)
22
+ const newline = bytes.indexOf(10)
23
+ const firstLine = bytes.subarray(0, newline < 0 ? length : newline)
24
+ if (firstLine.length > 255 || !/^#![ \t]*\/[^\s\0]+(?:[ \t]|$)/.test(firstLine.toString()) || firstLine.includes(0)) {
25
+ return `入口 ${entry} 缺少有效 shebang;请在首行指定解释器绝对路径,例如 #!/bin/sh,首行不得超过 255 字节(不含换行)。`
26
+ }
27
+ } finally {
28
+ closeSync(descriptor)
29
+ }
30
+ } catch {
31
+ return `无法读取或执行入口 ${entry};请检查文件、链接目标与读取和执行权限(chmod +x)。`
32
+ }
33
+ return null
34
+ }
35
+
36
+ function inspectDeclaration(contract, errors) {
37
+ const report = (location, message) => errors.push(`dx/contract.json ${location}:${message}`)
38
+ const fields = (value, allowed, location) => {
39
+ if (!isObject(value)) {
40
+ report(location, '必须是对象。')
41
+ return false
42
+ }
43
+ if (Object.keys(value).some(key => !allowed.includes(key))) report(location, '存在未知字段;请仅保留契约规定的字段。')
44
+ for (const key of allowed) {
45
+ if (!own(value, key)) report(`${location}.${key}`, '缺少必填字段。')
46
+ }
47
+ return true
48
+ }
49
+ const capability = (value, location, unit = false) => {
50
+ if (!isObject(value)) {
51
+ report(location, '能力声明必须是对象。')
52
+ return
53
+ }
54
+ if (Object.keys(value).some(key => !['status', 'reason'].includes(key))) report(location, '能力声明存在未知字段;仅允许 status 和 reason。')
55
+ if (!own(value, 'status') || !['implemented', 'mock'].includes(value.status)) report(`${location}.status`, '必须为 implemented 或 mock。')
56
+ if (own(value, 'reason') && (typeof value.reason !== 'string' || !value.reason.trim())) report(`${location}.reason`, '必须是非空字符串。')
57
+ if (value.status === 'mock' && !own(value, 'reason')) report(`${location}.reason`, 'mock 必须提供非空的不适用原因。')
58
+ if (unit && value.status === 'mock') report(location, 'unit 必须真实实现,不能声明为 mock。')
59
+ }
60
+ const targets = (value, location, { required, forbidden = [], unit = false } = {}) => {
61
+ if (!fields(value, ['targets'], location)) return
62
+ const declarations = value.targets
63
+ if (!isObject(declarations) || Object.keys(declarations).length === 0) {
64
+ report(`${location}.targets`, '必须声明至少一个目标。')
65
+ return
66
+ }
67
+ if (required && !own(declarations, required)) report(`${location}.targets`, `必须显式声明聚合目标 ${required}。`)
68
+ Object.entries(declarations).forEach(([target, declaration], index) => {
69
+ // Use ordinals instead of application-defined keys to keep diagnostics free of argument values.
70
+ const targetLocation = `${location}.targets[${index + 1}]`
71
+ if (!target.trim() || target.startsWith('-') || /[\u0000-\u001f\u007f]/.test(target)) report(targetLocation, '目标名必须非空、不得以连字符开头或包含控制字符。')
72
+ if (forbidden.includes(target)) report(targetLocation, '此命令不允许使用该保留聚合目标。')
73
+ capability(declaration, targetLocation, unit)
74
+ })
75
+ }
76
+
77
+ if (!fields(contract, ['schemaVersion', 'commands'], '$')) return
78
+ if (contract.schemaVersion !== 1) report('schemaVersion', '仅支持 schemaVersion: 1;请按 dx 0.3.0 契约迁移。')
79
+ if (!fields(contract.commands, standardCommands, 'commands')) return
80
+ const commands = contract.commands
81
+ capability(commands.setup, 'commands.setup')
82
+ targets(commands.start, 'commands.start', { required: 'stack', forbidden: ['all'] })
83
+ for (const command of ['build', 'lint', 'deploy']) targets(commands[command], `commands.${command}`, { required: 'all', forbidden: ['stack'] })
84
+ if (fields(commands.test, ['types'], 'commands.test') && fields(commands.test.types, testTypes, 'commands.test.types')) {
85
+ for (const type of testTypes) targets(commands.test.types[type], `commands.test.types.${type}`, { required: 'all', forbidden: ['stack'], unit: type === 'unit' })
86
+ }
87
+ if (fields(commands.db, ['actions'], 'commands.db') && fields(commands.db.actions, databaseActions, 'commands.db.actions')) {
88
+ for (const action of databaseActions) targets(commands.db.actions[action], `commands.db.actions.${action}`, { forbidden: ['all', 'stack'] })
89
+ }
90
+ }
91
+
92
+ export function inspectContract(root) {
93
+ const errors = []
94
+ const scripts = join(root, 'dx/scripts')
95
+ try {
96
+ const entries = readdirSync(scripts).sort()
97
+ for (const command of standardCommands) {
98
+ if (!entries.includes(command)) errors.push(`缺少必选入口 ${join(scripts, command)};请创建带 shebang 且可执行的应用脚本。`)
99
+ }
100
+ for (const name of entries) {
101
+ const error = inspectScript(join(scripts, name))
102
+ if (error) errors.push(error)
103
+ }
104
+ } catch {
105
+ errors.push('无法扫描 dx/scripts;请创建脚本目录并检查读取和搜索权限。')
106
+ }
107
+ let contract = null
108
+ try {
109
+ contract = JSON.parse(readFileSync(join(root, 'dx/contract.json'), 'utf8'))
110
+ } catch {
111
+ errors.push('无法读取或解析 dx/contract.json;请创建有效 JSON 声明,并按 dx-adopt 的 0.3.0 迁移指引补齐契约。')
112
+ return { contract, errors }
113
+ }
114
+ inspectDeclaration(contract, errors)
115
+ return { contract, errors }
116
+ }
117
+
118
+ export function assertTarget(contract, command, positionals) {
119
+ if (!standardCommands.includes(command) || command === 'setup') return
120
+ const declaration = contract?.commands?.[command]
121
+ let targets
122
+ let target
123
+ if (command === 'test' || command === 'db') {
124
+ const [kind, requestedTarget] = positionals
125
+ const variants = command === 'test' ? declaration?.types : declaration?.actions
126
+ targets = isObject(variants) && own(variants, kind) ? variants[kind]?.targets : undefined
127
+ target = requestedTarget
128
+ } else {
129
+ targets = declaration?.targets
130
+ target = positionals[0]
131
+ }
132
+ if (!isObject(targets) || typeof target !== 'string' || !own(targets, target)) {
133
+ throw new Error('请求的目标或调用组合未在 dx/contract.json 中声明;请使用已声明目标,或更新应用实现与契约。')
134
+ }
135
+ }
package/lib/env.js CHANGED
@@ -3,23 +3,14 @@ import { join } from 'node:path'
3
3
  import { parse } from 'dotenv'
4
4
  import { expand } from 'dotenv-expand'
5
5
 
6
- const flags = new Map([
7
- ['--prod', 'production'], ['--staging', 'staging'], ['--dev', 'development'],
8
- ['--test', 'test'], ['--e2e', 'e2e'],
9
- ])
10
6
  const nodeEnvironments = new Map([
11
7
  ['development', 'development'], ['staging', 'production'], ['production', 'production'],
12
8
  ['test', 'test'], ['e2e', 'test'],
13
9
  ])
14
10
 
15
- export function loadEnvironment(root, args, inherited = process.env) {
16
- const separator = args.indexOf('--')
17
- const selectors = separator < 0 ? args : args.slice(0, separator)
18
- const explicit = [...flags].find(([flag]) => selectors.includes(flag))?.[1]
19
- const requested = String(explicit || inherited.APP_ENV || inherited.NODE_ENV || 'development').toLowerCase()
20
- const environment = requested === 'local' || requested === 'dev' ? 'development' : requested
11
+ export function loadEnvironment(root, environment, inherited = process.env) {
21
12
  if (!nodeEnvironments.has(environment)) {
22
- throw new Error('无法选择环境;请使用 --dev、--prod、--staging、--test、--e2e,或设置有效的 APP_ENV / NODE_ENV。')
13
+ throw new Error('无法加载环境;命令必须提供有效的环境选择。')
23
14
  }
24
15
 
25
16
  const env = { ...inherited, APP_ENV: environment, NODE_ENV: nodeEnvironments.get(environment) }
@@ -0,0 +1,79 @@
1
+ import { standardCommands } from './contract.js'
2
+
3
+ const environments = new Map([
4
+ ['--dev', 'development'], ['--staging', 'staging'], ['--prod', 'production'],
5
+ ['--test', 'test'], ['--e2e', 'e2e'],
6
+ ])
7
+ const usages = {
8
+ setup: 'dx setup [环境标志] [-- 应用参数...]',
9
+ start: 'dx start <目标|stack> [环境标志] [-- 应用参数...]',
10
+ build: 'dx build <目标|all> [环境标志] [-- 工具参数...]',
11
+ lint: 'dx lint <目标|all> [环境标志] [-- 工具参数...]',
12
+ deploy: 'dx deploy <目标|all> [环境标志] [-- 应用参数...]',
13
+ test: 'dx test <unit|e2e> <目标|all> [-- 工具参数...]',
14
+ db: 'dx db <migrate|seed|reset|status> <目标> [环境标志] [-- 应用参数...]',
15
+ check: 'dx check',
16
+ }
17
+
18
+ export function commandHelp(command) {
19
+ const usage = usages[command]
20
+ if (!usage) return null
21
+ const detail = command === 'check'
22
+ ? '静态检查 dx/contract.json 和全部公开入口;不加载环境或执行应用。'
23
+ : command === 'test'
24
+ ? 'unit 固定使用 test 环境,e2e 固定使用 e2e 环境;禁止环境标志。'
25
+ : '默认 dev;最多一个环境标志:--dev、--staging、--prod、--test、--e2e。'
26
+ return `用法:${usage}\n${detail}\n-h / --help 显示本帮助,不执行应用。\n`
27
+ }
28
+
29
+ export function parseInvocation(command, args) {
30
+ const separator = args.indexOf('--')
31
+ const head = separator < 0 ? args : args.slice(0, separator)
32
+ const tail = separator < 0 ? [] : args.slice(separator)
33
+ const standard = standardCommands.includes(command)
34
+ if ((standard || command === 'check') && head.some(arg => arg === '--help' || arg === '-h')) {
35
+ return { help: commandHelp(command) }
36
+ }
37
+ if (command === 'check') {
38
+ if (args.length) throw new Error('check 不接受参数;请使用 dx check 或 dx check --help。')
39
+ return { args: [], positionals: [] }
40
+ }
41
+
42
+ const positionals = []
43
+ const forwarded = []
44
+ const selectors = []
45
+ for (const arg of head) {
46
+ if (environments.has(arg)) {
47
+ selectors.push(environments.get(arg))
48
+ } else if (arg === '--help' || arg === '-h') {
49
+ forwarded.push(arg)
50
+ } else if (arg.startsWith('-')) {
51
+ throw new Error('未知选项;应用或工具专属参数必须放在首个 -- 后。')
52
+ } else {
53
+ positionals.push(arg)
54
+ forwarded.push(arg)
55
+ }
56
+ }
57
+ if (command === 'test' && selectors.length) {
58
+ throw new Error('test 命令禁止环境标志;请删除标志,由 unit/e2e 确定环境。')
59
+ }
60
+ if (selectors.length > 1) {
61
+ throw new Error('最多允许一个环境标志;不同标志同时出现或相同标志重复均不允许。')
62
+ }
63
+ if (standard) {
64
+ const count = command === 'setup' ? 0 : command === 'test' || command === 'db' ? 2 : 1
65
+ if (positionals.length !== count || positionals.some(arg => !arg.trim())) {
66
+ throw new Error(`位置参数不符合契约;用法:${usages[command]}`)
67
+ }
68
+ if (command === 'test' && !['unit', 'e2e'].includes(positionals[0])) {
69
+ throw new Error('测试类型仅支持 unit 或 e2e;请使用 dx test <unit|e2e> <目标|all>。')
70
+ }
71
+ if (command === 'db' && !['migrate', 'seed', 'reset', 'status'].includes(positionals[0])) {
72
+ throw new Error('数据库动作仅支持 migrate、seed、reset、status;其他操作请使用扩展命令。')
73
+ }
74
+ }
75
+ const environment = command === 'test'
76
+ ? positionals[0] === 'unit' ? 'test' : 'e2e'
77
+ : selectors[0] || 'development'
78
+ return { args: [...forwarded, ...tail], positionals, environment }
79
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@weotro/dx",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -19,17 +19,24 @@
19
19
  "lib/logger.js",
20
20
  "lib/version.js",
21
21
  "lib/env.js",
22
+ "lib/contract.js",
23
+ "lib/invocation.js",
22
24
  "lib/setup.js",
23
25
  "lib/codex-initial.js",
24
26
  "skills",
25
27
  "example/README.md",
26
28
  "example/.env.development",
29
+ "example/dx/contract.json",
30
+ "example/dx/lib/arguments.mjs",
31
+ "example/dx/lib/arguments.test.mjs",
32
+ "example/dx/lib/commands.mjs",
27
33
  "example/dx/scripts/setup",
28
34
  "example/dx/scripts/start",
29
35
  "example/dx/scripts/build",
30
36
  "example/dx/scripts/test",
31
37
  "example/dx/scripts/db",
32
38
  "example/dx/scripts/deploy",
39
+ "example/dx/scripts/lint",
33
40
  "LICENSE",
34
41
  "README.md",
35
42
  "package.json"
@@ -1,88 +1,61 @@
1
1
  ---
2
2
  name: dx-adopt
3
- description: 将使用 @weotro/dx 0.1.x 的项目迁移到 0.2.1,或为新项目、未接入项目直接接入 0.2.1;实现应用脚本、适配环境与 worktree setup,并验证实际开发和 CI 入口。
3
+ description: 为应用接入或切换到 @weotro/dx 0.3.0,补齐命令入口和能力声明;提供自包含向导、接入经验及回归验收。
4
4
  ---
5
5
 
6
- # DX 0.2.1 项目接入与升级
6
+ # DX 0.3.0 接入与切换
7
7
 
8
- 在用户指定的应用仓库完成接入;未指定时使用当前仓库。适用于已有 monorepo 和新项目,不要求使用 Nx、Prisma 或 PM2。产出能运行的项目脚本和接入说明,不仅修改版本号。只读咨询则给出基于该项目的差异和迁移方案。
8
+ 在用户指定的应用仓库完成接入,未指定时使用当前仓库。保留现有包管理器和工具链,不要求 Nx、Prisma 或 PM2。产出可运行入口、声明及验收证据;只读咨询给出项目差异和方案。本技能及参考自包含,安装后无需访问 dx 源码仓库。
9
9
 
10
- 本技能随 dx 分发,引用文件均位于本技能目录,安装后无需访问 dx 源码仓库。适用的是 0.2.1 调用契约;项目实际安装包与本文不同时,先核对包版本、CLI 帮助和实现证据再修改。
10
+ ## 获取随包文档
11
11
 
12
- ## 1. 确定项目状态
12
+ 使用实际安装的 0.3.0 执行 `dx initial` 后,本文件、[0.3.0 应用契约](references/dx-0.3-contract.md)、[接入与切换向导](references/switch-to-0.3.md)和[经验与回归检查](references/adoption-regressions.md)均位于 `~/.agents/skills/dx-adopt/`,`~/.claude/skills/dx-adopt` 链接到同一份内容。应用可直接要求“使用 $dx-adopt 将当前项目切换到 dx 0.3.0 并完成验证”。
13
13
 
14
- 读取适用的 AGENTS.md、工作区差异、package.json、锁文件、CI 和实际任务入口。确认目标是使用 dx 的应用,而非误把 dx 工具仓库当成应用。保留现有包管理器和工具链。
14
+ 以上资料随 npm 包和技能完整分发,不需要源码仓库。需要包内 README 或可运行示例时,全局安装目录为 `npm root -g` 输出下的 `@weotro/dx`;项目安装目录为 `node_modules/@weotro/dx`。版本与安装来源须和实际执行的 dx 一致。
15
15
 
16
- 核对 Node 版本、dx 的实际来源和版本:区分全局 `dx`、项目依赖与 CI 安装步骤。可用 `command -v dx`、`dx --version` 和项目包管理器的依赖查询核对;CLI 不存在时从依赖与调用点继续盘点,不阻塞脚本编写。不要用会自动下载其他版本的命令代替版本核验。
16
+ ## 1. 确定项目与版本
17
17
 
18
- 按证据选择路径:
18
+ 读取 AGENTS.md、工作区差异、package.json、锁文件、CI 和真实任务入口,确认目标为使用 dx 的应用。核对全局 dx、项目依赖和 CI 的实际来源及版本;可用 command -v dx、dx --version 和依赖查询。CLI 缺失时继续盘点,不用自动下载其他版本的命令替代核验。
19
19
 
20
- | 项目状态 | 执行路径 |
21
- | --- | --- |
22
- | 依赖 0.1.x,或仍使用 dx/config、旧 internal runner、旧 CLI 参数 | 阅读 [0.1.x 迁移](references/migrate-0.1.md),建立旧能力到新脚本的对应关系后实施 |
23
- | 从未接入 dx,或新建项目直接采用 0.2.1 | 跳过旧配置迁移,从现有工具命令生成下文应用入口 |
24
- | 已是 0.2.x,但部分入口缺失或行为错误 | 对齐目标版本并对照契约只补缺口,保留已工作的脚本,避免重复生成 |
20
+ 先读 [0.3.0 应用契约](references/dx-0.3-contract.md),按[接入与切换向导](references/switch-to-0.3.md)完成入口、声明、调用方和验收。schemaVersion 为 1;首次接入从真实工具命令生成入口,已有项目保留实际功能并直接对齐 0.3.0,已符合契约的部分按行为证据复核。
25
21
 
26
- 三条路径都先阅读 [0.2.1 能力与调用契约](references/dx-0.2-contract.md)。文件中说明哪些能力由 dx 提供,哪些必须由应用实现。
22
+ 沿用安装方式,对齐开发、依赖和 CI 的版本策略,实际版本须支持 0.3.0 契约。尚未发布时用明确的本地待测包,不把 registry latest 或声明版本当作行为证明。保留锁文件,全局项目不顺手增加本地 dx;本地依赖须有独立安装引导,全局 dx 可在应用依赖安装前 setup。仅有兼容性证据时调整应用 Node 工具链。
27
23
 
28
- ## 2. 落实版本与入口
24
+ ## 2. 实现入口与声明
29
25
 
30
- 沿用项目的安装方式,将应用依赖、锁文件、开发说明与 CI 对齐到同一个已核验的 0.2.1 包。项目已有 dx 依赖就更新原有声明;全局安装的项目明确记录全局命令和 CI 的安装步骤,不顺手引入第二套入口。检查所用 registry 是否提供目标版本;尚未发布时可使用用户提供的构建包验证,如实记录阻塞,不用 latest 冒充 0.2.1。
26
+ 补齐 setup/start/build/test/db/deploy/lint 七个合法、可执行、带 shebang 的入口,按 [JSON 示例](references/dx-0.3-contract.md#契约声明)编写 dx/contract.json。标准语法和字段严格遵循参考,按完整调用组合声明 implemented 或 mock,显式列出必需聚合,由应用实现聚合行为。
31
27
 
32
- 全局安装采用明确版本的示例为 `npm install -g @weotro/dx@0.2.1`。全局 dx 可在应用尚未安装依赖时运行 setup;只放在应用 devDependencies 中的 dx 不能假定新 clone 后已可执行,必须保留安装 dx 的引导步骤。
28
+ 不适用能力仍提供 mock:声明非空原因,实际输出命令名称及原因并返回 0,验收记为跳过。unit 所有目标必须真实实现;需要但未完成的能力不能冒充不适用。implemented 声明不是行为证据。
33
29
 
34
- 在约定的项目根创建 `dx/scripts/`,只实现需要的命令:
30
+ 公开入口放 dx/scripts,辅助实现和测试可放 dx/lib,应用策略可放 dx/config;后两者不是 dx 自动加载契约。保留现有合理布局,迁移目录同步 import、根路径、工具 inputs、fixture、CI 和文档。无扩展名 Node 脚本的模块模式应匹配项目及最低 Node 版本,不能假定项目为 ESM。
35
31
 
36
- | 入口 | 应用需要实现的内容 |
37
- | --- | --- |
38
- | setup | 基础复制完成后的依赖安装、必要文件生成等准备;可重复执行并保留用户本地配置 |
39
- | start | 已存在的启动目标;monorepo 的 stack 必须映射到项目定义的全部服务,单应用只启动指定服务 |
40
- | build | 根据实际目标和 APP_ENV 调用构建工具,明确需要的 NODE_ENV 与工具配置 |
41
- | test | 单元、集成、E2E 的分流、路径参数和所需服务,不能靠命令名猜测 dx 的环境选择 |
42
- | db | 实际数据库子命令、前置检查及确认规则 |
43
- | deploy | 项目部署过程、环境检查、确认、失败处理和恢复 |
32
+ 薄包装调用真实实现,避免 dx → package script → dx 循环,不依赖 dx 私有 lib 或恢复旧配置解释器。独立职责可用扩展,不写入标准声明;check/initial 为保留名称。
44
33
 
45
- 脚本名无扩展名,带有效 shebang 并设置可执行权限。内部逻辑可放入项目自己的 `.mjs` / `.cjs` 或 Shell 文件;无扩展名 Node 脚本的模块模式应与目标项目及最低 Node 版本匹配,不能假定每个项目都是 ESM。
34
+ lint 表达静态检查,单测及脚本守卫归入 test unit,独立集成测试使用扩展。追查 dependsOn 和 package forwarder,避免下层仍隐式混合。组合验证采用清楚命名扩展或显式序列。精简与审查读 [命令职责与精简](references/adoption-regressions.md#命令职责与精简)。
46
35
 
47
- 若已有项目脚本可复用,使用薄包装。例如确认 `scripts/dx/build.mjs` 已实现后:
36
+ ## 3. 参数、环境与准备
48
37
 
49
- ```sh
50
- #!/bin/sh
51
- exec node scripts/dx/build.mjs "$@"
52
- ```
38
+ 命令决定环境,APP_ENV/NODE_ENV 只传递结果。test unit 固定 test、test e2e 固定 e2e,禁止额外环境标志;其他应用命令含扩展默认 dev,最多一个环境标志,重复也报错。应用直接消费结果,不再次解析 dx 标志或根据 NODE_ENV 重新选择环境层。
53
39
 
54
- 包装调用实际工具或实现文件,避免 `dx build` → package script → `dx build` 的循环。不要导入 dx 私有 lib、恢复旧配置解释器,或为每个项目重建通用任务框架。原有 lint 等任务可通过同名脚本继续暴露;`initial` 是 dx 保留命令。
40
+ dx 删除自身消费的环境标志,保留类型、动作、目标、首个 -- 和后续每项参数。工具专属选项放在 -- 后,按数组转交,明确下游是否消费分隔符;不把后续内容重新当作 dx 选项,不拼接 shell 命令或 eval。
55
41
 
56
- 实现项目帮助和未知目标的明确失败。需要的入口必须调用真实实现;不需要的入口可以不创建。没有完成的构建、测试、数据库或部署步骤不能用 `echo` / `exit 0` 占位后宣称接入完成。
42
+ dx 加载公共两层环境,应用不重复加载;额外服务文件明确所有者与覆盖关系。dx start admin --prod 或 dx start admin --staging 仍在当前机器运行,不改成 SSH 或远程部署。应用可为构建子进程单独控制优化模式,同时保留选定应用配置。
57
43
 
58
- ## 3. 接好参数、环境与准备顺序
44
+ setup 顺序为全量契约及输入检查 → 缺失 .local 复制 → 环境加载 → 应用 setup。先补齐全部入口和声明,通过 dx check 再 setup。应用 setup 幂等且不递归调用 dx setup;缺少凭据时说明准备方式,不能生成虚假秘密。应用负责 start stack 成员、顺序、失败与停止方式,所有入口按真实进程树幂等清理后代。
59
45
 
60
- 按契约使用 dx 注入的环境,不再自行运行相同两层 dotenv 加载。项目若有额外服务专属文件,明确由哪个脚本加载及覆盖关系。
46
+ ## 4. 更新使用方与验收
61
47
 
62
- 实现和说明 `start` 时,将环境选择与运行位置分开:`dx start admin --prod` 使用当前机器项目根的 `.env.production`、`.env.production.local` 在当前机器启动 admin。`--prod` / `--staging` 等只选择配置及 APP_ENV / NODE_ENV;应用 start 应保持本机启动语义,不将这些标志解释成 SSH、远程启动或部署。具体示例与资源连接边界见契约的“环境”部分。
48
+ 同步 package scripts、CI、容器及部署调用、测试服务启动命令、开发说明和 agent 指令。旧功能清理须有去向与消费者切换证据。升级、审查及参数/环境/构建/进程/布局变化,按 [适配回归检查](references/adoption-regressions.md)相关分支取得隔离与真实运行证据。
63
49
 
64
- dx 识别环境标志但不会从 argv 删除它们。应用消费首个 `--` 前属于环境选择的标志后,再把工具参数逐项传给工具;`--` 后的数据不得再次当作 dx 标志解析。明确工具是否需要保留或消费这个分隔符。禁止把 argv 重新拼成 shell 命令或用 eval。
50
+ 逐项记录:
65
51
 
66
- setup 的顺序是 **dx 复制缺失的 .local 文件 → dx 加载环境 → 应用 setup**。先创建有效的应用 setup 入口,基础复制才会执行。应用 setup 不必再次调用 dx 的基类,也不能递归执行 `dx setup`。新 clone 没有本地凭据来源时,给出准备方法,不能凭空生成有效秘密。
52
+ - 实际安装源与版本,根和子目录发现正确工程,入口执行权限能随 clone 保留。
53
+ - dx check 静态合规;隔离无效声明、缺入口或损坏扩展时,环境加载、setup 复制和业务未发生。
54
+ - 非敏感标记验证实际两层覆盖、默认环境、继承变量不能选环境、冲突标志拒绝及最终工具 argv,不输出真实环境集合。
55
+ - fresh clone 依赖引导成立,linked worktree 缺失 .local 在 setup 前可用且已有值不覆盖;无主 checkout 单独说明。
56
+ - 真实 unit 运行通过,其他 implemented 能力取得相关运行证据;每个 mock 验证日志命令、原因及退出码 0,记为跳过。start 验证单目标、stack 成员及后代收尾。
57
+ - db/deploy 路由、前置校验与失败行为有相关验证,未执行真实部署明确记录,不将模拟或返回 0 当作发布成功。
67
58
 
68
- 对构建、E2E、预发和生产选择分别核对 APP_ENV / NODE_ENV。需要测试层的调用明确使用 `--test` 或 `--e2e`;需要生产构建的应用在自己的构建实现中控制工具行为。不要恢复旧 dx 对 Next/Nx 命令文本的隐式识别。
59
+ 标准帮助无业务副作用,dx setup --help 不复制文件;扩展帮助及 -- --help 是真实调用,不假定无副作用或通用 dry-run。按既有授权处理真实数据库和部署,其余用本地隔离资源验证。
69
60
 
70
- 确认 `start stack` 的服务成员、依赖顺序、失败策略和停止方式。多进程由应用收尾,Ctrl-C 与 SIGTERM 处理要幂等;仅创建多个后台进程后返回成功不算启动适配完成。
71
-
72
- ## 4. 更新使用方并验收
73
-
74
- 更新 package scripts、CI、容器或部署调用、开发说明与相关 agent 指令,让它们使用已实现的新入口。旧项目的删除时机与覆盖表按迁移参考执行;新项目无需创建兼容文件或迁移旧功能。
75
-
76
- 使用目标项目自身的验证方式,按实际副作用选择测试环境。项目适配不自动包含生产部署、正式数据库迁移或远端发布;已有明确授权的操作继续执行,否则先用本地、隔离资源或项目已有的模拟入口验证对应链路。不要把真实部署当作通用冒烟。
77
-
78
- 验收应得到以下证据,而非仅列命令:
79
-
80
- - `dx --version` 对应所选安装源,`dx --help` 在根和子目录发现正确入口;用 Git 文件模式确认脚本执行权限会随 clone 保留。
81
- - 在隔离工程用非敏感标记值验证两层覆盖、显式环境选择及 argv 边界;不输出真实环境变量集合。
82
- - 新 clone 的依赖引导能成立;linked worktree 中缺失的 .local 在应用 setup 前可用,已有本地值不覆盖。没有主 checkout 的环境单独记录,不能假定 CI 自动拥有本地文件。
83
- - 应用 setup、实际构建与相关测试通过。验证 start 的单应用和 stack 成员及退出收尾;无相应服务时注明不适用。
84
- - db/deploy 若属需求,其参数路由、前置校验与失败行为有测试;未获授权的生产执行记录为未执行,不写成部署验证通过。
85
-
86
- `dx --help` 只做发现;`dx <command> --help` 会运行应用脚本,尤其 `dx setup --help` 仍会先复制文件。只有确认项目帮助处理无业务副作用后,才把它用作冒烟。不要假定 dx 提供通用 dry-run。
87
-
88
- 交付说明写清项目走哪条路径、安装源与版本、已实现命令及用法、环境文件与 setup 行为、实际验证、未完成项。升级任务同时给出旧能力的去向;首次接入只给出新增入口。未指定提交或发布时,完成本地改动与验证即可。
61
+ 涉及分发时,在隔离 home 验证 initial 同步本技能及全部参考;涉及 dx 包交付时检查运行模块、示例契约、lint 和技能参考入包。交付区分静态通过、真实通过、mock 跳过及未验证项,说明版本、命令、环境、setup 和旧能力去向。未指定提交或发布时完成本地改动与验证即可。
@@ -0,0 +1,106 @@
1
+ # 适配回归检查
2
+
3
+ 适用于 DX 升级审查及相关修复。以下来自实际迁移中“入口测试通过,但真实行为错误”的案例;按项目涉及的分支使用,不要求其他项目采用相同框架、服务名或脚本实现。
4
+
5
+ 本文随 0.3.0 技能分发。标准接口以[应用契约](dx-0.3-contract.md)为准,接入顺序见[切换向导](switch-to-0.3.md)。
6
+
7
+ ## 命令职责与精简
8
+
9
+ 先从帮助与实际路由建立“入口 → 实现 → 消费者 → 副作用”的对应关系,再决定删除、合并或保留。迁移旧版不等于逐个复刻旧命令,也不等于删除所有自定义扩展。
10
+
11
+ - 相同实现的别名优先收敛为一个明确入口;安装、清理和缓存包装若没有项目特有职责,可回到项目允许的包管理器入口。`setup` 的依赖安装与必要生成仍是完整准备流程,不能仅因包含 install 就视为重复。
12
+ - 同类操作遵守标准位置语法:deploy/build 只接一个目标,工具专属动作或模式放 -- 后;独立计划、品牌、翻译及私有环境管理可使用明确扩展。七个必选入口仍须齐全。
13
+ - 目标名必须符合覆盖范围:曾有 `deploy all` 实际只部署默认后端,应改为明确 target 或真正的全量编排。帮助首页展示简短用途,目标与参数留给命令级帮助。
14
+ - 合并部署入口只改变路由,不绕过原适配器的环境校验、确认、产物清理、扫描及远端核验;分别保留只读 plan、远程 run、单目标部署与本地制品构建的边界。
15
+
16
+ ### lint 与 test 的边界
17
+
18
+ 遇到 lint 混入测试时追查真实调用链。静态检查使用 dx lint all 或明确目标,默认 dev;单测使用 dx test unit all,E2E 使用 dx test e2e all,自动选择测试环境且禁止额外环境标志。
19
+
20
+ 一次实际拆分中,除了顶层先跑后端单测,`scripts:lint` 还执行整套 `node --test`。只删除顶层 test 调用并没有完成拆分。把脚本测试移到独立 test target,保留分类 forwarder、注册覆盖、缓存策略和明确执行入口;原来要求两类检查的后端验证与 CI 改为显式运行 lint 和 test,避免测试覆盖随改名丢失。参数也应按新职责核验,例如 `--fix` 只传给静态检查,脚本测试聚合入口若不支持过滤就明确拒绝,不能静默忽略。
21
+
22
+ ### 迁移与验收
23
+
24
+ 同步运行脚本、package scripts、CI、生成器内调用、帮助、开发说明及 AGENTS.md;机械替换后复核语义,避免留下“Web deploy 已被阻断”或“lint 已包含单测”等过期结论。历史执行记录保留原命令,当前操作指引使用新入口。
25
+
26
+ 用隔离替身捕获实际 argv 和调用次数:普通 lint 不调用测试、fix 只影响静态检查、独立测试仍选对环境;合并入口到达原安全适配器,未知目标或不支持的参数在业务工具启动前失败。按影响范围跑已有注册表与适配回归,区分替身验证、真实工具运行和未执行的全仓检查,不因命令精简而运行真实发布。
27
+
28
+ 核对根与子目录发现及帮助。删除扩展需处理公开入口和消费者,七个标准入口保持齐全。dx/scripts 只放合法公开入口,辅助模块移到 dx/lib 等目录;全量检查也会拒绝损坏扩展及保留名称。标准帮助由 dx 提供,应用工具帮助经 -- --help,扩展帮助仍执行应用。
29
+
30
+ ## 环境选择与隔离
31
+
32
+ DX 在应用入口运行前按命令类型选择环境。直接调用 dx test unit/e2e 加目标,不在已加载 development 的包装入口内再次切到测试环境,以免继承普通变量污染或影响展开。仅覆盖 APP_ENV/NODE_ENV 无法清除继承普通变量。
33
+
34
+ 同步 CI、Playwright webServer、测试内启动命令和说明:test unit/e2e 不传环境标志;其启动的非测试 dx 命令默认仍为 dev,需显式选择如 dx start stack --e2e。调用者 APP_ENV/NODE_ENV 不能选环境。
35
+
36
+ 隔离工程用不同的非秘密标记值验证:
37
+
38
+ - development 独有变量不会由入口自动切换环境带入 E2E;错环境入口在启动测试或连接服务前失败。
39
+ - test e2e 的展开结果仅来自调用者普通变量及 E2E 两层;.env.test 和 development 都不是前置层。已有 APP_ENV/NODE_ENV 不改变选择。
40
+ - 公开文件覆盖继承环境、local 覆盖公开文件;CI 的同名值和占位符也包含在覆盖测试内。
41
+
42
+ 需要把旧 `.env.test.local` 的缺项迁到 `.env.e2e.local` 时,只迁测试需要的键,保留目标已有值与文件权限,不输出秘密。实际测试使用的 DSN、播种参数、Redis 和登录凭据须在独立环境中齐备。
43
+
44
+ 项目将 DX 定为环境加载来源时,还要检查下游编排器是否再次加载 dotenv。使用 Nx 的项目可在调用边界设置 `NX_LOAD_DOT_ENV_FILES=false`,覆盖单应用、Stack 及其他直接启动 Nx 的路径;这不会自动关闭 Vite 等工具自身的加载行为。用下游可能读取文件中的独有标记值验证没有额外注入,仅断言开关已传递不足以证明最终环境隔离。
45
+
46
+ ## 构建配置与优化模式
47
+
48
+ APP_ENV 表示应用配置,NODE_ENV/工具 mode 影响打包行为。`dx build ... --dev` 仍可能需要开发 API 地址和生产优化,默认继承 NODE_ENV=development 会把 React 开发版打进产物。
49
+
50
+ 按项目要求在构建子进程显式控制优化模式,保留选定的应用配置;不要全局改变 start/test 的模式。同步 Nx 或其他构建缓存的相关 inputs、模式参数及实现文件依赖,确保修复后不会复用旧的开发版制品。
51
+
52
+ 验收至少包含真实构建及产物检查:使用适合实际框架的开发版特征、构建元数据或运行行为确认优化生效,同时检查 API 等非秘密配置仍属于目标环境。构建退出 0 或模拟命令中的 NODE_ENV 正确不单独构成产物证据;已有缓存不能替代此次实际构建。
53
+
54
+ ## 参数边界
55
+
56
+ dx 已消费环境标志并处理标准帮助;应用按首个 -- 分开位置参数和工具参数,不重新解析 dx 环境标志。为每个入口明确透传支持及下游分隔符处理,不支持的工具参数明确失败。
57
+
58
+ 隔离捕获下游 argv,覆盖 `-- --help`、`-- --prod`、带空格的过滤条件、空参数及未知选项。`--` 后的内容不参与 dx 的帮助或环境解析,应用按约定交给工具处理。经过 Nx、JSON 环境载体等多层包装时,验证最终工具收到的数组;补一个真实过滤测试,确认只执行选定用例,避免参数被中间层丢弃。真实资源相关测试仍受项目授权边界约束。
59
+
60
+ ## 单应用与 Stack 进程回收
61
+
62
+ 仅向直接子进程转发信号可能留下 pnpm/Nx/Vite 等后代。单应用和多服务入口都要验证包装器退出、后代另建进程组、后代忽略 SIGTERM 的情况;正常结束、启动失败和重复信号也需要幂等收尾。
63
+
64
+ 使用项目可维护的进程管理方式,保留退出码或信号状态,并在有限等待后清理仍属于本次运行的后代。归属可结合所创建进程组、记录的身份或随机运行标识判断;发信号前重新核对归属,避免 PID 复用或误伤其他 Stack。端口、进程名不能单独作为批量结束进程的依据。
65
+
66
+ 回收失败须独立报告:底层已有非零退出码时保留原码,底层成功但回收失败时返回非零。覆盖“底层已退出、异步回收尚未完成时收到 SIGINT/SIGTERM”的时序;监听保留到收尾结束,最终结算中断状态,不能因提前保存成功码而把取消报告为成功。
67
+
68
+ 平台陷阱:
69
+
70
+ - Linux `/proc` 查找方式不适用于 macOS;需要支持 macOS 时验证对应发现逻辑,尤其父包装器退出后的孤儿后代。
71
+ - macOS 对空组或仅剩僵尸的进程组可能返回 EPERM。只有成功检查并确认组内无活进程时才按已结束处理;真实权限错误及检查失败仍须报告,不能一概吞掉 EPERM。
72
+ - 读取进程环境用于运行标识匹配时,结果只在内部使用,避免把环境集合写进测试日志或异常。
73
+
74
+ 真实冒烟应等待本地服务就绪,再向最外层 dx 发 SIGTERM,核对退出状态、端口释放和后代存活情况;Ctrl-C 路径也按改动范围验证。隔离探针用 finally 清理自身进程,测试失败也要回收。不能用“dx 已退出”替代服务已停止的证据。
75
+
76
+ ## 环境文件布局保护
77
+
78
+ 旧 env-policy 若约束文件位置,需逐项保留 allowRoot、allowSubdirGlobs、禁止路径、秘密占位符与 local-only 键等实际要求。DX 不加载某个文件,不表示 Vite 等下游工具也不会加载它;只检查必需变量不足以代替布局校验。
79
+
80
+ 使用目标项目策略构建允许及禁止样例,例如 `.env.preview`、应用目录 `.env.local`,确认拒绝发生在工具启动前。扫描同时考虑未跟踪文件;按项目范围跳过生成目录、依赖目录和独立嵌套 worktree,避免误报其他 checkout 或遍历其秘密。用允许路径正例防止检查退化为一律拒绝。没有旧布局要求的项目按实际工具需要制定规则,不复制别的项目环境名单。
81
+
82
+ ## 目录迁移与准备流程
83
+
84
+ `dx/scripts/` 是公开入口契约;`dx/lib/`(实现与配套测试)和 `dx/config/`(项目策略)是可选的集中布局。移动时核对:
85
+
86
+ - 包装入口、相对 import、基于 import.meta.url 的根定位、测试复制 fixture。
87
+ - Nx targets/inputs、lint glob、容器 COPY、CI、部署配置、文档与 agent 指令。
88
+ - 根与子目录调用、Git 可执行文件模式、至少一个真实命令;搜索旧路径确认没有活动消费者。
89
+
90
+ setup 使用隔离 Git 主 checkout、linked worktree 和独立 clone 验证:复制发生在应用 setup 前,已有 local 不覆盖、权限保留,独立 clone 不继承另一 clone 的秘密。应用 setup 保持幂等;仅存在 `dx/config/` 不等于有效的 `dx/scripts/setup`。
91
+
92
+ 再次 setup 不会刷新已有 `.local` 内容;需要补充配置时逐项处理缺项,保留现有值和权限。缺少环境文件也不会自动阻止数据库或部署,必需变量、连接目标和确认条件由应用在工具启动前验证。
93
+
94
+ 切换入口时保留仍被使用的 Prisma schema、PM2 ecosystem、Nx 配置和 dotenv 依赖。只清理已经没有消费者的包装或策略文件;端口冲突处理、服务准备、重试与失败恢复需在项目实现中有明确归属,不能因为统一了命令入口就遗漏。
95
+
96
+ ## 数据库、部署与失败归因
97
+
98
+ 读取目标项目授权规则并核实实际连接目标。项目已授权 DEV/E2E reset、重建或迁移时,可在核实目标后处理校验和冲突等环境问题并重跑,不重复询问同一授权;这不是其他项目的默认授权。staging/production 的操作需要该项目用户针对环境及操作的明确授权,本地启动和 `--yes` 不能代替授权。
99
+
100
+ 部署先用隔离替身验证参数、目标、确认、失败短路及构建次数,按实际部署工具版本核对 CLI 参数。明确区分模拟验证、帮助核对与真实发布,不为证明迁移成功自行触发远程部署。
101
+
102
+ 检查部署平台能否绕过项目 deploy 包装器直接执行 buildCommand。若发布构建依赖包装器注入环境标志,缺失或空值须在构建前失败,避免默认为 development;Shell 可用 `${DX_ENV_FLAG:?DX_ENV_FLAG is required}`。用替身验证缺失、空值及合法值,确认失败时未启动构建。普通本地开发构建不因此强制使用发布环境。
103
+
104
+ 部署凭据采用工具支持的非 argv 通道;例如核实所用 Vercel CLI 支持后通过 `VERCEL_TOKEN` 传递,避免 `--token` 暴露在进程参数和命令日志中。用假令牌检查最终 argv 与输出。CI 需要临时 local 凭据文件时,在创建文件前注册清理,再写入并运行命令,覆盖写入或后续步骤失败的路径。
105
+
106
+ 失败按复现证据区分本次回归、既有问题与环境问题;必要时在不覆盖当前工作区的隔离基线复现。相关问题解决并复验后更新已知失败记录。纯技能或文档更新只检查结构、引用与差异,不因此重跑业务全仓测试。
@@ -0,0 +1,99 @@
1
+ # DX 0.3.0 应用契约
2
+
3
+ 本文随 0.3.0 的 npm 包和 dx-adopt 技能独立分发。实际接入按[0.3.0 向导](switch-to-0.3.md)执行,排查应用适配问题时参考[经验与回归检查](adoption-regressions.md)。
4
+
5
+ ## 入口与语法
6
+
7
+ 工程发现标记为最近的 `dx/scripts` 目录,其父级 dx 的父目录是工程根,也是应用进程 cwd。最近工程损坏或缺少入口时失败,不回退外层工程。七个标准入口全部必选,每个是无扩展名、可读可执行的文件;名称匹配 `[a-z][a-z0-9-]*`,shebang 指定绝对解释器路径,首行不超过 255 字节(不含换行)。辅助模块放在公开入口目录外。
8
+
9
+ | 入口 | 必填位置参数 | 必需聚合 |
10
+ | --- | --- | --- |
11
+ | setup | 无,额外位置参数报错 | 整个工程 |
12
+ | start | `<目标>` | stack,不接受 all |
13
+ | build / lint / deploy | `<目标>` | all,不接受 stack |
14
+ | test | `<unit\|e2e> <目标>` | 两种类型各自 all,不接受 stack |
15
+ | db | `<migrate\|seed\|reset\|status> <目标>` | 无,禁止 all/stack |
16
+
17
+ 缺少或多出位置参数、未知类型/动作/目标明确报错。目标由应用声明,all/stack 的成员、顺序与失败行为由应用实现,dx 不自动编排。扩展命令可自由定义位置参数,但遵守公共选项规则;独立集成测试及额外数据库动作使用明确命名的扩展。check/initial 为保留名称,不得提供同名应用脚本。
18
+
19
+ 首个 `--` 前只接受规定的位置参数、五种环境标志和 -h/--help;未知选项报错,工具专属参数放在 `--` 后。dx 删除自身消费的环境标志,保留类型、动作、目标、首个 `--` 和之后每项原始参数,包括空字符串及空格,不重新拼接 shell 命令。
20
+
21
+ ```text
22
+ dx build admin --prod -- --mode fast
23
+ 应用 argv: ["admin", "--", "--mode", "fast"]
24
+ dx test unit all -- --watch
25
+ 应用 argv: ["unit", "all", "--", "--watch"]
26
+ ```
27
+
28
+ 标准命令 -h/--help 由 dx 显示统一语法,允许缺少位置参数;不运行应用、环境加载或 setup。应用/工具帮助通过合法调用后的 `-- --help` 请求。扩展帮助交给应用执行,仍先检查契约并加载环境,不能视为无副作用。
29
+
30
+ ## 契约声明
31
+
32
+ 工程根 `dx/contract.json` 必须为 JSON 对象,只包含 `schemaVersion` 和 `commands`。版本必须为数字 1;commands 必须恰好包含七个标准命令,扩展不用声明。各层只允许示例所示字段:setup 为状态对象;其他命令通过 targets、types 或 actions 定位完整调用组合。
33
+
34
+ 以下示例表示一个有真实单测、其余能力确实不适用的库项目。只有确认项目现状符合时才采用对应 mock;有实际服务或构建任务时改为 implemented 并实现它们。
35
+
36
+ ```json
37
+ {
38
+ "schemaVersion": 1,
39
+ "commands": {
40
+ "setup": { "status": "implemented" },
41
+ "start": { "targets": { "stack": { "status": "mock", "reason": "库项目没有常驻服务" } } },
42
+ "build": { "targets": { "all": { "status": "mock", "reason": "直接发布源码,无构建步骤" } } },
43
+ "lint": { "targets": { "all": { "status": "implemented" } } },
44
+ "deploy": { "targets": { "all": { "status": "mock", "reason": "库项目没有部署服务" } } },
45
+ "test": {
46
+ "types": {
47
+ "unit": { "targets": { "all": { "status": "implemented" } } },
48
+ "e2e": { "targets": { "all": { "status": "mock", "reason": "库项目没有端到端应用流程" } } }
49
+ }
50
+ },
51
+ "db": {
52
+ "actions": {
53
+ "migrate": { "targets": { "main": { "status": "mock", "reason": "项目不使用数据库" } } },
54
+ "seed": { "targets": { "main": { "status": "mock", "reason": "项目不使用数据库" } } },
55
+ "reset": { "targets": { "main": { "status": "mock", "reason": "项目不使用数据库" } } },
56
+ "status": { "targets": { "main": { "status": "mock", "reason": "项目不使用数据库" } } }
57
+ }
58
+ }
59
+ }
60
+ }
61
+ ```
62
+
63
+ 每个状态对象只有 status 和可选 reason;status 为 implemented 或 mock,mock 必须附非空字符串原因。所有 unit 目标必须 implemented。start stack、build/lint/deploy all、unit/e2e all 必须显式声明;db 四个动作各至少一个目标,可分别使用 main。添加目标时在对应 targets 中声明状态;不允许通过任意新字段添加环境策略、工具命令或编排。
64
+
65
+ mock 由应用脚本实际执行,输出包含命令名称和不适用原因的日志,返回 0 表示跳过。声明 mock 不会让 dx 自动跳过脚本。需要但尚未完成的能力不能声称不适用;缺少单测不能靠 mock 履约。
66
+
67
+ ## 静态检查与行为证据
68
+
69
+ `dx check` 聚合入口与声明的静态错误,成功返回 0,失败返回 1;不加载环境、不复制文件、不调用业务脚本。全量检查包括全部公开扩展入口,不只检查即将调用的脚本。检查名称、文件、权限、shebang、保留名称、必需声明、组合、状态与 mock 原因。
70
+
71
+ 真实应用调用(含扩展帮助)须通过全量检查,再在环境加载、setup 复制与应用执行前通过本次参数、目标和环境校验。顶层及标准命令帮助、版本和 initial 不因应用契约缺失或损坏被拦截。新接入先补齐入口及声明,再 check 和 setup;没有兼容模式或绕过开关。
72
+
73
+ 静态合规不证明业务真实实现。dx 不扫描 echo/exit 等文本猜测实现,也不为 check 试运行脚本。dx-adopt 分别取得真实 unit 等功能证据、mock 日志与退出码证据,明确未验证项;implemented 声明本身不是验收结果。
74
+
75
+ ## 环境
76
+
77
+ 命令是环境选择唯一来源,APP_ENV/NODE_ENV 仅传递结果。test unit 固定 test,test e2e 固定 e2e,首个 `--` 前禁止任何环境标志,包括冗余 --test/--e2e。其他应用命令含扩展默认 development,可传 --dev/--staging/--prod/--test/--e2e 中一个;不同或重复标志都报错。首个 `--` 后的标志不参与解析。
78
+
79
+ | 命令选择 / APP_ENV | NODE_ENV | 环境文件后缀 |
80
+ | --- | --- | --- |
81
+ | development | development | development |
82
+ | staging | production | staging |
83
+ | production | production | production |
84
+ | test | test | test |
85
+ | e2e | test | e2e |
86
+
87
+ 例如 `APP_ENV=production dx build all` 仍为 development;生产构建配置使用 `dx build all --prod`。环境标志只选择当前机器的配置,不改变执行机器,也不自动部署;start 保持本机启动,资源实际位置由配置地址决定。
88
+
89
+ 从工程根按低到高加载:继承普通环境变量 < `.env.<环境>` < `.env.<环境>.local`。APP_ENV/NODE_ENV 在变量展开前由命令确定,不被继承值或文件改变。仅合并子进程环境,不修改 dx 自身 process.env。dotenv 解析引号、注释及多行值,dotenv-expand 展开同层已定义变量、前层与继承变量。文件缺失跳过,读取失败停止;通用 .env/.env.local、应用子目录和旧 env-layers.json 不自动加载。CI 普通同名变量也可能被文件覆盖。
90
+
91
+ 应用直接消费注入结果,不重复解析 dx 环境标志或根据 NODE_ENV 重新选择环境层。额外工具 dotenv 加载、构建优化模式及必要值校验属于应用,验收其真实效果。
92
+
93
+ ## setup、进程与分发
94
+
95
+ setup 顺序为:全量契约及本次输入检查 → 基础 worktree 复制 → 环境加载 → 应用 setup。linked worktree 从主 Git 工作目录复制所有以 .local 结尾的普通文件(含隐藏及被忽略的文件),按相对路径写入当前 Git worktree 根,保留权限,跳过已有文件/目录/链接。跳过源符号链接、Git 元数据及嵌套 worktree,目标父路径必须为实际目录;复制失败停止应用,已复制文件保留。主工作目录、非 Git 工程、没有主 checkout 的 bare 仓库 worktree 跳过复制。Git 需安装。复制 .env.local 不代表加载它,独立 clone 不共享秘密。其他命令不触发 setup。
96
+
97
+ dx 继承标准流、保持交互终端、返回应用退出状态并转发 SIGINT/SIGTERM;应用幂等清理自己的后代,dx 不监督后台服务。单进程 Shell 包装可用 exec。工具选择、安装、编排、数据库确认和部署恢复均由应用负责。
98
+
99
+ 运行要求 Node.js >=20.11.0,macOS/Linux;全局 dx 可在应用依赖尚未安装时调用 setup,本地依赖需要独立引导。initial 无需应用工程,不运行 setup;使用包内 skills 更新 ~/.agents/skills 的托管技能、建立 ~/.claude/skills 链接并清理托管旧副本及历史删除技能,其他技能保留。技能外部工具另行安装认证。验证分发使用隔离 home,记录实际包版本与引用完整性。