Skip to content

Commit 1697d5c

Browse files
fix(cli): os lint warns on every bare package-authored metadata name, and os generate writes them prefixed (#30842)
<!-- claude-projects-attribution: {"github_login":"os-elon-musk"} --> _Requested by **Elon** · [project thread](https://claude.ai/code/project/chan_01WedDzV26nniwxv7jC88B9D?thread=cmsg_01WedDzV26nniwxv7jC88B9D9HSxC7WngZADjXgb6UHCVp)_ domain:devx#1 · os-elon-musk · session_016WV4cyadQ5Zhih2y1GNTQm Part of #30804 Clause-②: no Metadata-protocol: none ## 维护者速读 - **改了什么**:`os lint` 的 `naming/namespace-prefix` 从「只报包内重名」扩为「每个裸的包内元数据名都报」(28 类),`fix` 给出 `<ns>_<name>`;`os generate` 写出的脚手架名同步带前缀。 - **为什么改**:裁决 C(#30798「同意 c」)——包作者写的每个元数据名都带命名空间;本 PR 是第二阶段的 lint 一半,示例与模板改名在 PR 2。 - **风险与回滚**:仍是 warning,不拒收;只有 `os lint --strict` 会因它失败,仓内没有 CI 车道这样跑示例。回滚即 revert。 - **先看**:① `lint.ts` 的 `namespacePrefixIssues` 与 `NAME_JUDGED_ELSEWHERE`(豁免表);② `project-namespace.ts` 的 `carriesPackageNamespace`(lint 与 generate 共用的一个判定)。 - **两分钟验证**:`pnpm --filter @objectstack/cli exec vitest run test/lint-namespace-prefix.test.ts`;或在 `examples/app-todo` 跑 `os lint`,看 20 条新警告各带 `fix`。 - **需要你做的**:裁一个开放问题——`showcase.export_data` 这类点号能力名是否算已带命名空间(本 PR 默认算,见 Acceptance notes)。 ## What changes **`naming/namespace-prefix` warns on every bare package-authored name.** In a package that declares `manifest.namespace`, every top-level item in every manifest collection (`PLURAL_TO_SINGULAR`, 28 of its 31 keys) whose `name` does not carry the namespace gets one warning. The message states the contract, and the `fix` is the rename: ```text ⚠ Flow "onboard" does not carry the package namespace "crm". Every metadata name a package authors starts with its namespace, as object names do, so that no two installed packages can hold the same name — rename it to "crm_onboard" and update every reference that spells it. ``` The judged collections are apps, pages, dashboards, reports, datasets, actions, flows, jobs, positions, permissions, capabilities, sharingRules, queues, assignmentRules, apis, webhooks, agents, tools, skills, ragPipelines, hooks, mappings, picklists, analyticsCubes, connectors, datasources, emailTemplates and books. The set is derived from the spec map, so a collection added there is judged the day it lands. **What is not judged, and why** (`NAME_JUDGED_ELSEWHERE` plus the predicate): | Not judged | Reason | |:--|:--| | `objects` | `defineStack`'s `validateNamespacePrefix` already refuses a bare object name | | `views` | a container is named for its bound object; a view item is `<object>.<key>` | | `docs` | the doc lint's own `docs/namespace-prefix` error | | object / picklist extensions, translations, fields, `functions` | not in the collection map: each names the item it extends, has no name of its own, is keyed inside its object, or is a code handler | | an item that declares `overrides` | ADR-0146 D2: it replaces the base package's item of the same name | | a package with no `manifest.namespace` | nothing to prefix with, the object rule's posture | A name **carries** the namespace when it is the namespace itself (the ADR-0019 namespace-named app), starts with `<ns>_`, starts with `<ns>.` (the dotted capability shape `CapabilitySchema` documents, `billing.refund`), or starts with the platform-reserved `sys_`. That is one predicate, `carriesPackageNamespace` in `utils/project-namespace.ts`. **The duplicate finding stays**, under the same rule id, for any name declared twice in one package, prefixed or not (apps, pages, dashboards, flows, reports, datasets, and actions on their `objectName:name` key). It no longer offers the prefixed spelling as its rename, because the new finding carries that, and its message no longer says the prefix is "an optional convention". It now has no `fix`. **`os generate` writes prefixed names (bounded in-place fix, accepted by the seat).** Before, only object names got the prefix, so in a namespaced project `os g flow onboard` wrote `onboard_flow`, and the new warning fired on a fresh scaffold. Now the action, flow, dashboard, app, skill, picklist, permission set and dataset generators write `<ns>_…` through `namespacedItemName`, which uses the same predicate, so a scaffold is never written in a spelling the lint reports. A name that already carries the namespace is never doubled. The namespace is now read for every generator. The refusal when the config does not load still applies only to `namesObject` generators, because a bare non-object name is a warning and not a refusal. The `namesObject` flag still means "writes an object name". **Changeset:** `'@objectstack/cli': patch`. ## Why Ruling C on #30798 (maintainer, verbatim 「同意 c」): every package-authored metadata name carries its namespace, phased. Two installed packages that share a bare name such as `send_email` often resolve to only one of them, silently. This is phase 2's lint half. Phase 1 (#30803) is the registration backstop, phase 3 (#30806) turns it into a refusal, and #30805 covers the skills. PR 2 of this card renames the examples and templates. ## Acceptance notes - **Open question for a ruling (dotted capability names).** app-showcase declares `showcase.export_data` and `showcase.restricted_ops` in namespace `showcase`. CapabilitySchema's name regex admits a dot, and its own example is `billing.refund`. This PR treats `<ns>.` as carrying the namespace. Its test has a twin: `billing.refund` under namespace `crm` still warns. If the ruling is strict `<ns>_` only, the dotted clause comes out of `carriesPackageNamespace`, and PR 2 renames the two capabilities and their three `requiredPermissions` references. Phase 3 (#30806) needs the same answer. - **Open question for phase 3: `functions`.** They are not in the collection map, so this rule does not judge them. The audit behind #30798 named function handlers as a collision class, so #30806 may want them in its refusal. - **Baseline bare-name counts under this rule** (origin/main `100303c0`, before PR 2). Duplicates are 0 everywhere. | Config | Bare | By type | |:--|--:|:--| | `examples/app-todo` | 20 | actions 8, reports 5, flows 4, dashboards 1, datasets 1, hooks 1 | | `examples/app-crm` | 7 | positions 3, dashboards 1, datasets 1, permissions 1, hooks 1 | | `examples/app-showcase` | 12 | positions 10, sharingRules 2 (14 under a strict `<ns>_` reading: plus capabilities 2) | | `examples/app-multi-package` | 1 | apps 1 (`multi_crm` in package `crm`, so the fix is `crm_multi_crm`) | | `create-objectstack` blank template | 0 | only `blank_note`; every other barrel is empty | | `examples/embed-objectql` | 0 | declares no manifest namespace | - **No CI lane turns these warnings red.** The rule is reached only through `lintConfig` (`os lint`, `os lint --eval`), never from `os validate`, `os build` or `os compile`. `os lint` exits 1 on warnings only under `--strict`, and nothing in `.github/workflows`, `scripts/` or the example and template `package.json` files passes it. `check:i18n-coverage` counts only `i18n/` rules. The tests that lint real examples filter to their own rule or assert exit 0. A warning costs 3 points of the `os lint` score, and no example's score is pinned. - **Pins this PR changes on purpose.** The old tests asserted that a bare name is silent, and those assertions are rewritten. `generate-object-namespace-prefix.test.ts`'s census of #20197 ("prefixes every object name, and only object names") now expects every scaffold's own name prefixed. Its `namesObject` derivation holds the scaffold's own name fixed, except on the object scaffold. `generate-scaffold-validates.test.ts`'s #21325 binding check pins the scaffold's own `name` on its own and searches only the rest of the artifact for a stray object reference. `generate-stack-reach.test.ts` expects `my_app_approve` / `my_app_approval_changed_flow`. - **ADR-0048 §5's lint bullet** is out of date. Per the seat's route, it is corrected in its own one-file `docs/adr` PR (Tier H), not here. - **Out of scope here:** `skills/objectstack-platform/SKILL.md:121` still describes the old rule (#30805). `content/docs/ui/doc-pages.mdx` L70–83 says the prefix is "no longer load-bearing for uniqueness" (PR 2's docs pass). ## Human Minimum - [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic. (`packages/spec` not touched.) - [x] 2. Every name I added uses the casing for its kind. - [x] 3. No formatter ran; `pnpm lint` passes. (eslint `--no-inline-config` on every changed file: exit 0.) - [x] 4. One issue, one branch off an up-to-date `main`, one pull request. - [x] 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset. - [x] 6. `packages/spec` touched: its generated artifacts are regenerated and committed. (n/a) - [x] 7. No hand edits to `content/docs/references/`, `packages/*/CHANGELOG.md` or `content/docs/releases/`. - [x] 8. Title, body and comments are in English. - [x] The *Before Submitting* list in CONTRIBUTING.md is done. ## Verification All at head `c4ad5c80` (origin/main `d996c381` merged in). - `pnpm --filter @objectstack/cli exec vitest run` (unit + integration projects): 590 files passed, 1 skipped; 7272 tests passed, 9 skipped. - The nightly e2e tier (`OS_TEST_TIERS=nightly`) on the 19 files that generate, scaffold, init or lint (`generate-*.e2e`, `init-created-files-summary.e2e`, `scaffold-emission-policy.e2e`, `starter-field-consumers.e2e`, `jsx-gate-manifest-notice.e2e`, `lint-*.e2e`): 19 files, 250 tests, all passed. - `pnpm --filter @objectstack/cli typecheck` (`tsc --noEmit` and `check:test-typecheck`): exit 0. The test layer needed `--max-old-space-size=8192`, because it ran out of heap at the default. - eslint `--no-inline-config` on the 9 changed `.ts` files: exit 0. - `pnpm --filter @objectstack/spec run check:liveness`: exit 0. - `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack`: exit 0, 61 commands, all run, all exit 0. Two first exited 3 (PREREQUISITE NOT MET) and went green on a re-run once the prerequisite was met. `check-plugin-teardown-shape --self-test` needed its pinned fixture commit fetched into the shallow clone. `check:dual-build-cjs-loads` needed 7 packages built (studio, client-react, embedder-openai, knowledge-memory, knowledge-ragflow, service-cluster-redis, service-knowledge). `--ran` reconciliation: exit 0, 61 derived, 61 run, 0 NOT-MEASURED. <details><summary>The 61 gates and their exit codes</summary> ```text node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0 node scripts/check-adr-0087-registration.mjs --self-test :: exit 0 node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0 node scripts/check-closing-keyword-parity.mjs :: exit 0 node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0 node scripts/check-comment-mask-adoption.mjs :: exit 0 node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0 node scripts/check-comment-mask-corpus.mjs :: exit 0 node scripts/check-dts-emitted.mjs --self-test :: exit 0 node scripts/check-empty-changeset.mjs --base origin/main :: exit 0 node scripts/check-empty-changeset.mjs --self-test :: exit 0 node scripts/check-issue-citations.mjs :: exit 0 node scripts/check-plugin-teardown-shape.mjs :: exit 0 node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0 node scripts/ci/assert-turbo-remote-cache.mjs --self-test :: exit 0 node scripts/ci/plan-test-core-shards.mjs --self-test :: exit 0 node scripts/ci/prune-turbo-cache.mjs --self-test :: exit 0 node scripts/docs-audit/check-affected-docs.mjs :: exit 0 node scripts/docs-audit/check-drift-comment.mjs :: exit 0 node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0 node scripts/release-pending-publish.mjs --self-test :: exit 0 pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0 pnpm check:changeset-gate-self-tests :: exit 0 pnpm check:ci-filter-parity :: exit 0 pnpm check:cli-test-child-env :: exit 0 pnpm check:closed-ceremony-text :: exit 0 pnpm check:cross-package-test-inputs :: exit 0 pnpm check:dispatcher-error-vocabulary :: exit 0 pnpm check:doc-authoring :: exit 0 pnpm check:driver-memory-census :: exit 0 pnpm check:dts-closure :: exit 0 pnpm check:dual-build-cjs-loads :: exit 0 pnpm check:engine-double-contract :: exit 0 pnpm check:gitlink-declared :: exit 0 pnpm check:i18n :: exit 0 pnpm check:i18n-coverage :: exit 0 pnpm check:i18n-walk-parity :: exit 0 pnpm check:issue-citations :: exit 0 pnpm check:keyed-text-bounds :: exit 0 pnpm check:lean-entry-closure :: exit 0 pnpm check:logger-receiver-detach :: exit 0 pnpm check:nul-bytes :: exit 0 pnpm check:objectql-double-limit :: exit 0 pnpm check:org-identifier :: exit 0 pnpm check:page-declaration-shape :: exit 0 pnpm check:platform-object-tenancy-census :: exit 0 pnpm check:published-files :: exit 0 pnpm check:query-options-erasure :: exit 0 pnpm check:refd-timer-probe :: exit 0 pnpm check:registry-log-declared :: exit 0 pnpm check:rest-log-spy-declared :: exit 0 pnpm check:slot-lookup :: exit 0 pnpm check:sourcemap-no-sources-content :: exit 0 pnpm check:system-context-census :: exit 0 pnpm check:test-source-alias :: exit 0 pnpm check:tier-file-adoption :: exit 0 pnpm check:type-check-coverage :: exit 0 pnpm check:type-check-debt :: exit 0 pnpm check:undeclared-dep-imports :: exit 0 pnpm check:watch-hint-literal :: exit 0 pnpm check:where-matcher :: exit 0 ``` </details> --- _Generated by [Claude Code](https://claude.ai/code/session_016WV4cyadQ5Zhih2y1GNTQm)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 6ff27b3 commit 1697d5c

10 files changed

Lines changed: 621 additions & 248 deletions
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
'@objectstack/cli': patch
3+
---
4+
5+
fix(cli): `os lint`'s `naming/namespace-prefix` warns on every bare package-authored metadata name, and no longer calls the prefix optional
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
Every metadata name a package authors now carries its namespace, the way object names already do: two installed packages that share a bare name such as `send_email` often resolve to only one of them, silently. `os lint` says so for every metadata type a stack can declare (flows, actions, hooks, picklists, pages, dashboards, reports, datasets, apps, email templates, webhooks, connectors, permission sets, agents, tools, skills, jobs and the rest), with the rename as the finding's `fix`:
11+
12+
```text
13+
⚠ Flow "onboard" does not carry the package namespace "crm". … rename it to "crm_onboard" …
14+
```
15+
16+
It stays a warning: nothing is refused, and `os lint --strict` is the only way it fails a run.
17+
18+
Not judged, because the name is judged elsewhere or derived from another item: objects (`defineStack` already refuses a bare object name), views (named for their object), docs (the doc lint's `docs/namespace-prefix`), object and picklist extensions, fields, translation bundles, `functions`, and an item that declares `overrides` (it must keep the base package's name). A name that is the namespace itself, starts with `sys_`, or starts with `<namespace>.` (the dotted capability shape, `billing.refund`) carries it already. A package that declares no `manifest.namespace` is not judged.
19+
20+
`os generate` writes the names it scaffolds the same way. In a project that declares a namespace, `os g flow onboard` now writes `crm_onboard_flow`, and the same goes for actions, dashboards, apps, skills, picklists, permission sets and datasets. Before, only object names got the prefix, so a fresh scaffold drew this warning on its first lint. A name that already carries the namespace is not prefixed twice.
21+
22+
The intra-package duplicate warning stays under the same rule id, for a prefixed name declared twice as well. It no longer suggests the prefixed spelling as its rename (the new finding carries that) and no longer ends with the sentence saying the prefix is an optional convention.

‎packages/cli/src/commands/generate.ts‎

Lines changed: 65 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ import { validateObjectNamespacePrefix } from '@objectstack/spec/kernel';
5959
import { singularToPlural } from '@objectstack/spec/shared';
6060
import { printHeader, printSuccess, printError, printInfo, printStep, printWarning, createTimer, isReportedError, CLI_ALIAS } from '../utils/format.js';
6161
import { metadataFileName } from '../utils/metadata-file-name.js';
62-
import { readProjectNamespace, type ProjectNamespace } from '../utils/project-namespace.js';
62+
import { namespacedItemName, readProjectNamespace, type ProjectNamespace } from '../utils/project-namespace.js';
6363
import { findEmissionParseFailures } from '../utils/emitted-source-parses.js';
6464
import { findBarrelAliasRefusal } from '../utils/importable-binding.js';
6565
import {
@@ -150,15 +150,19 @@ export interface ScaffoldBinds {
150150
* In a project whose manifest declares a `namespace`, that is
151151
* `${namespace}_${name}`, which is the name `defineStack`'s namespace-prefix
152152
* gate demands. `namesObject` says which generators write one, so that
153-
* `runMetadataGeneration` reads the manifest only for those.
154-
* `generate-object-namespace-prefix.test.ts` pins both the prefix and the
155-
* flag against the templates.
153+
* `runMetadataGeneration` refuses for those when the config does not load:
154+
* with the namespace unknown, the object name it would write is one
155+
* `defineStack` may refuse. `generate-object-namespace-prefix.test.ts` pins
156+
* both the prefix and the flag against the templates.
156157
*
157-
* Only object names are prefixed. The scaffold's own `name` on an action, a
158-
* flow, a dashboard, an app, a skill or a picklist is not judged against the
159-
* namespace by any gate `os validate` runs, so it stays the name the author
160-
* typed. A view container writes no `name` at all: it is registered under
161-
* the object it binds to (#21325, see the `view` generator).
158+
* Every other metadata name carries it too (ruling C on #30798). The
159+
* scaffold's own `name` on an action, a flow, a dashboard, an app, a skill, a
160+
* picklist, a permission set or a dataset goes through {@link itemNameFor}:
161+
* `${namespace}_${name}` in a namespaced project, unless the derived name
162+
* already carries the namespace. `os lint`'s `naming/namespace-prefix` warns
163+
* on a bare one, and `generate-scaffold-validates.test.ts` holds every
164+
* scaffold to zero findings. A view container writes no `name` at all: it is
165+
* registered under the object it binds to (#21325, see the `view` generator).
162166
*
163167
* ## A binding is read off the stack, never derived from the new item's name
164168
*
@@ -418,8 +422,8 @@ export default ${toCamelCase(name)}Views;
418422
*/
419423
namesObject: true,
420424
binds: { object: 'flag', flow: 'flag' },
421-
itemName: (name: string) => toSnakeCase(name),
422-
generate: (name: string, _namespace?: string, bindings?: ScaffoldBindings) => {
425+
itemName: (name: string, namespace?: string) => itemNameFor(toSnakeCase(name), namespace),
426+
generate: (name: string, namespace?: string, bindings?: ScaffoldBindings) => {
423427
const object = requireBinding('action', bindings, 'object');
424428
const flow = requireBinding('action', bindings, 'flow');
425429
return `import * as UI from '@objectstack/spec/ui';
@@ -428,7 +432,7 @@ export default ${toCamelCase(name)}Views;
428432
* ${toTitleCase(name)} Action
429433
*/
430434
const ${toCamelCase(name)}Action: UI.Action = {
431-
name: '${toSnakeCase(name)}',
435+
name: '${itemNameFor(toSnakeCase(name), namespace)}',
432436
label: '${toTitleCase(name)}',
433437
// Runs the flow named in \`target\` against the record it is invoked on.
434438
type: 'flow',
@@ -494,9 +498,9 @@ export default ${toCamelCase(name)}Action;
494498
*/
495499
namesObject: true,
496500
binds: { object: 'flag' },
497-
itemName: (name: string) => `${toSnakeCase(name)}_flow`,
501+
itemName: (name: string, namespace?: string) => itemNameFor(`${toSnakeCase(name)}_flow`, namespace),
498502
requires: FLOW_SCAFFOLD_REQUIRES,
499-
generate: (name: string, _namespace?: string, bindings?: ScaffoldBindings) => {
503+
generate: (name: string, namespace?: string, bindings?: ScaffoldBindings) => {
500504
const object = requireBinding('flow', bindings, 'object');
501505
return `import * as Automation from '@objectstack/spec/automation';
502506
@@ -509,7 +513,7 @@ export default ${toCamelCase(name)}Action;
509513
* not load.
510514
*/
511515
const ${toCamelCase(name)}Flow: Automation.Flow = {
512-
name: '${toSnakeCase(name)}_flow',
516+
name: '${itemNameFor(`${toSnakeCase(name)}_flow`, namespace)}',
513517
label: '${toTitleCase(name)} Flow',
514518
type: 'record_change',
515519
// Armed: the trigger below fires it. Set 'obsolete' to switch it off.
@@ -550,14 +554,14 @@ export default ${toCamelCase(name)}Flow;
550554
description: 'Analytics dashboard',
551555
defaultDir: 'src/dashboards',
552556
namesObject: false,
553-
itemName: (name: string) => `${toSnakeCase(name)}_dashboard`,
554-
generate: (name: string) => `import * as UI from '@objectstack/spec/ui';
557+
itemName: (name: string, namespace?: string) => itemNameFor(`${toSnakeCase(name)}_dashboard`, namespace),
558+
generate: (name: string, namespace?: string) => `import * as UI from '@objectstack/spec/ui';
555559
556560
/**
557561
* ${toTitleCase(name)} Dashboard
558562
*/
559563
const ${toCamelCase(name)}Dashboard: UI.Dashboard = {
560-
name: '${toSnakeCase(name)}_dashboard',
564+
name: '${itemNameFor(`${toSnakeCase(name)}_dashboard`, namespace)}',
561565
label: '${toTitleCase(name)} Dashboard',
562566
widgets: [],
563567
};
@@ -588,16 +592,16 @@ export default ${toCamelCase(name)}Dashboard;
588592
*/
589593
namesObject: true,
590594
binds: { object: 'flag' },
591-
itemName: (name: string) => `${toSnakeCase(name)}_app`,
592-
generate: (name: string, _namespace?: string, bindings?: ScaffoldBindings) => {
595+
itemName: (name: string, namespace?: string) => itemNameFor(`${toSnakeCase(name)}_app`, namespace),
596+
generate: (name: string, namespace?: string, bindings?: ScaffoldBindings) => {
593597
const object = requireBinding('app', bindings, 'object');
594598
return `import * as UI from '@objectstack/spec/ui';
595599
596600
/**
597601
* ${toTitleCase(name)} App
598602
*/
599603
const ${toCamelCase(name)}App: UI.App = {
600-
name: '${toSnakeCase(name)}_app',
604+
name: '${itemNameFor(`${toSnakeCase(name)}_app`, namespace)}',
601605
label: '${toTitleCase(name)}',
602606
navigation: [
603607
{
@@ -638,8 +642,8 @@ export default ${toCamelCase(name)}App;
638642
* harness default.
639643
*/
640644
namesObject: false,
641-
itemName: (name: string) => toSnakeCase(name),
642-
generate: (name: string) => `import { defineSkill } from '@objectstack/spec/ai';
645+
itemName: (name: string, namespace?: string) => itemNameFor(toSnakeCase(name), namespace),
646+
generate: (name: string, namespace?: string) => `import { defineSkill } from '@objectstack/spec/ai';
643647
644648
/**
645649
* ${toTitleCase(name)} Skill
@@ -653,7 +657,7 @@ export default ${toCamelCase(name)}App;
653657
* a startup error naming the key, not a field that goes missing later.
654658
*/
655659
const ${toCamelCase(name)}Skill = defineSkill({
656-
name: '${toSnakeCase(name)}',
660+
name: '${itemNameFor(toSnakeCase(name), namespace)}',
657661
label: '${toTitleCase(name)}',
658662
description: 'One line on what this skill is for — the model routes on it.',
659663
@@ -682,7 +686,7 @@ const ${toCamelCase(name)}Skill = defineSkill({
682686
// it (\`ai-skill-tool-unresolved\`), and at runtime the reference is dropped
683687
// while the instructions keep promising the capability.
684688
//
685-
// tools: ['action_${toSnakeCase(name)}', 'query_records'],
689+
// tools: ['action_${itemNameFor(toSnakeCase(name), namespace)}', 'query_records'],
686690
tools: [],
687691
});
688692
@@ -720,18 +724,18 @@ export default ${toCamelCase(name)}Skill;
720724
* `FieldSchema` refuses the two together.
721725
*/
722726
namesObject: false,
723-
itemName: (name: string) => toSnakeCase(name),
724-
generate: (name: string) => `import { definePicklist } from '@objectstack/spec/data';
727+
itemName: (name: string, namespace?: string) => itemNameFor(toSnakeCase(name), namespace),
728+
generate: (name: string, namespace?: string) => `import { definePicklist } from '@objectstack/spec/data';
725729
726730
/**
727731
* ${toTitleCase(name)} Picklist
728732
*
729733
* A shared option list. A select field offers these options by naming the
730-
* list — Field.select({ picklist: '${toSnakeCase(name)}' }) — and declares no
734+
* list — Field.select({ picklist: '${itemNameFor(toSnakeCase(name), namespace)}' }) — and declares no
731735
* \`options\` of its own: a field declaring both is refused.
732736
*/
733737
const ${toCamelCase(name)}Picklist = definePicklist({
734-
name: '${toSnakeCase(name)}',
738+
name: '${itemNameFor(toSnakeCase(name), namespace)}',
735739
label: '${toTitleCase(name)}',
736740
options: [
737741
{ label: 'Option A', value: 'option_a' },
@@ -779,8 +783,8 @@ export default ${toCamelCase(name)}Picklist;
779783
*/
780784
namesObject: true,
781785
binds: { object: 'flag' },
782-
itemName: (name: string) => toSnakeCase(name),
783-
generate: (name: string, _namespace?: string, bindings?: ScaffoldBindings) => {
786+
itemName: (name: string, namespace?: string) => itemNameFor(toSnakeCase(name), namespace),
787+
generate: (name: string, namespace?: string, bindings?: ScaffoldBindings) => {
784788
const object = requireBinding('permission', bindings, 'object');
785789
return `import { definePermissionSet } from '@objectstack/spec/security';
786790
@@ -793,7 +797,7 @@ export default ${toCamelCase(name)}Picklist;
793797
* by any one of them is granted.
794798
*/
795799
const ${toCamelCase(name)}PermissionSet = definePermissionSet({
796-
name: '${toSnakeCase(name)}',
800+
name: '${itemNameFor(toSnakeCase(name), namespace)}',
797801
label: '${toTitleCase(name)}',
798802
799803
// One entry per object, keyed by its machine name. readScope is which of
@@ -841,8 +845,8 @@ export default ${toCamelCase(name)}PermissionSet;
841845
*/
842846
namesObject: true,
843847
binds: { object: 'flag' },
844-
itemName: (name: string) => toSnakeCase(name),
845-
generate: (name: string, _namespace?: string, bindings?: ScaffoldBindings) => {
848+
itemName: (name: string, namespace?: string) => itemNameFor(toSnakeCase(name), namespace),
849+
generate: (name: string, namespace?: string, bindings?: ScaffoldBindings) => {
846850
const object = requireBinding('dataset', bindings, 'object');
847851
const dimension = object.displayField
848852
? `\n { name: ${tsString(object.displayField)}, field: ${tsString(object.displayField)} },\n `
@@ -857,7 +861,7 @@ export default ${toCamelCase(name)}PermissionSet;
857861
* name; it declares no object, field or aggregate of its own.
858862
*/
859863
const ${toCamelCase(name)}Dataset = defineDataset({
860-
name: '${toSnakeCase(name)}',
864+
name: '${itemNameFor(toSnakeCase(name), namespace)}',
861865
label: '${toTitleCase(name)}',
862866
object: ${tsString(object.name)},
863867
@@ -1093,6 +1097,19 @@ function prefixedObjectName(shortName: string, namespace?: string): string {
10931097
: `${namespace}_${shortName}`;
10941098
}
10951099

1100+
/**
1101+
* The NON-object machine name a scaffold writes (ruling C on #30798): every
1102+
* metadata name a package authors carries its namespace, the way an object
1103+
* name does. `shortName` is the name as the generator derives it
1104+
* (`task_done_flow`); it gains `${namespace}_` unless it already carries the
1105+
* namespace — the predicate `os lint`'s `naming/namespace-prefix` judges with,
1106+
* so a scaffold is never written in a spelling the lint reports. With no
1107+
* namespace it is written as derived.
1108+
*/
1109+
function itemNameFor(shortName: string, namespace?: string): string {
1110+
return namespacedItemName(shortName, namespace);
1111+
}
1112+
10961113
/**
10971114
* A TypeScript single-quoted string literal for `value`. Bindings carry text
10981115
* read off the author's stack (an object's label), so it is escaped rather
@@ -1585,7 +1602,7 @@ async function runMetadataGeneration(
15851602
console.log(chalk.dim(
15861603
// #20197: "always the same string" stopped being true for an object
15871604
// name in a namespaced project, which gains the manifest's prefix.
1588-
' write lands in the file unchanged (a namespaced project only prefixes an object name).',
1605+
' write lands in the file unchanged (a namespaced project only adds its namespace prefix).',
15891606
));
15901607
console.log(chalk.dim(
15911608
// ⛔ The examples are deliberately NOT built from what the author
@@ -1603,15 +1620,17 @@ async function runMetadataGeneration(
16031620

16041621
// The project's config, loaded once before anything is written.
16051622
//
1606-
// Its `manifest.namespace` is applied only by a generator that writes an
1607-
// object machine name (#20197) — see `objectNameFor` — and only such a
1608-
// generator refuses when the config does not load. Every generator reads
1609-
// it (#20215), because whether the config loaded BEFORE this command wrote
1610-
// anything is what the reach check below needs: a config that loaded then
1611-
// and does not load once the scaffold is in place was broken by this
1612-
// command, and the write is taken back out. A generator that names no
1613-
// object still generates into a project whose config does not load, as it
1614-
// always has.
1623+
// Its `manifest.namespace` prefixes an object machine name (#20197) — see
1624+
// `objectNameFor` — and every other metadata name a scaffold writes (ruling
1625+
// C on #30798) — see `itemNameFor`. Only a generator that writes an object
1626+
// name refuses when the config does not load, because `defineStack`
1627+
// refuses an unprefixed object; a bare non-object name is a lint warning,
1628+
// so the others still generate into such a project, as they always have,
1629+
// with the name as derived. Every generator reads it (#20215), because
1630+
// whether the config loaded BEFORE this command wrote anything is what the
1631+
// reach check below needs: a config that loaded then and does not load
1632+
// once the scaffold is in place was broken by this command, and the write
1633+
// is taken back out.
16151634
//
16161635
// BELOW the charset gate, because the prefix is a derivation and the
16171636
// #16726 position puts every derivation after that gate. ABOVE the render,
@@ -1644,8 +1663,8 @@ async function runMetadataGeneration(
16441663
console.log('');
16451664
process.exit(1);
16461665
}
1647-
if (project.kind === 'loaded') namespace = project.namespace;
16481666
}
1667+
if (project.kind === 'loaded') namespace = project.namespace;
16491668

16501669
// The object name derived from what the author TYPED as this item's name:
16511670
// the `object` scaffold's own name, and a view's, which names the object

0 commit comments

Comments
 (0)