Skip to content

os create plugin foo.bar emits export const foo.barPlugin — an npm-legal name that renders un-parseable TypeScript #15892

Description

@os-litant

Filed unassigned and bare by the os-dev seat that landed #15816 (branch claude/issue-15816-create-validates-project-name). ⛔ Not graded here — no domain:*, no type, no priority. Out of #15816's scope and deliberately not folded into its PR.

Measured

On origin/main b398ad258b9, driving the published entry with NO_COLOR=1, streams captured separately, exit code read before any pipe:

node packages/cli/bin/run.js create plugin foo.bar
  exit 0 — writes ./plugin-foo.bar/
  src/index.ts contains:  export const foo.barPlugin: Plugin = {

Parsed with TypeScript's own parser (ts.createSourceFile, syntactic diagnostics only):

foo.bar   -> 1 syntactic diagnostic     export const foo.barPlugin: Plugin = {
my-app    -> 0 syntactic diagnostics    (same template, control)

Why this is NOT the defect #15816 fixed, and is not closed by it

#15816 was about names npm refuses. foo.bar is a perfectly legal npm package name, and @objectstack/plugin-foo.bar is publishable. It passes validateProjectName() — the charset rule is ^[a-z0-9][a-z0-9._-]*$, which admits . on purpose, because a dot is legal in an npm name.

So the validation #15816 adds accepts this input, correctly, and the emitted project still does not compile. Verified after that fix: os create plugin foo.bar still exits 0 and still writes the broken source.

The mechanism

packages/cli/src/commands/create.ts:

function toCamelCase(str: string): string {
  return str.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
}

It only folds -x into X. Every other character legal in an npm name but illegal in a JS identifier passes through into an identifier position:

  • foo.bar becomes foo.barPlugin (a property access, not an identifier)
  • a leading digit (1foo, legal for npm) becomes 1fooPlugin
  • _ and ~ shapes are admitted by npm and reach the same position

The identifier is used twice in src/index.ts and twice more in the emitted README.md.

Why it was not fixed alongside #15816

The correct form is a design choice, not a mechanical one, and picking it inside another card's PR would be deciding it silently. At least three self-consistent answers exist:

  1. narrow what os create accepts further than npm does (reject any name that is not a clean identifier stem);
  2. sanitise the identifier only — keep the npm name as the user typed it, derive a valid identifier from it, the way sanitizeNamespace() already derives a namespace;
  3. stop deriving an identifier at all — emit a default export, or a fixed name.

Option 2 is the shape the repo already uses one line away, but it makes the exported symbol no longer match the package name, which is a user-visible naming decision.

⚠️ Whoever takes this should check it against #15530 (emitted scope, with the maintainer) and #15816's PR, so a third philosophy is not introduced into the same emitted manifest.

Re-check

node packages/cli/bin/run.js create plugin foo.bar
sed -n '1,8p' ./plugin-foo.bar/src/index.ts
grep -n "toCamelCase" packages/cli/src/commands/create.ts

Dedup

One targeted search_issues pass (repo-scoped REST is 403 for this seat, gh absent, so the REST list route was unavailable). Positive control: the query returned #15816, a known-indexed os create emission card, so it reached the index. Total hits 1, that one being #15816 itself. ⛔ Not exhaustive — a single semantic query, not a full-corpus sweep.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions