05 — CLI (qcobjects-cli)¶
Purpose¶
Specify the CLI: the developer's single entry point for scaffold, serve, build, publish, and version — with the complete command and server catalogue, so the CLI README and repo docs can be summarized without loss.
Sources: qcobjects-cli README + repo structure (v2.5.158); core README
§§ HTTP2 server, CLI tool.
Code pins: https://github.com/QCObjects/qcobjects-cli/blob/v2.5.158/src/<file>.ts.
Definitions below are authoritative.
Scope¶
Repo QCObjects/qcobjects-cli, npm qcobjects-cli (v2.5.158).
Sources in src/*.ts, binaries in bin/ (qcobjects-cli.js).
Node >= 22, npm >= 10; install with npm i --legacy-peer-deps.
Service model (normative)¶
- Server settings file:
/etc/qcobjects/config.json(service installs). service qcobjects status|start|stop|restart.- Scaffold:
qcobjects create mynewapp --pwa|--amp; serve viaqcobjects launch mynewapporqcobjects-server(serves CWD over HTTP/2 with default config). - Built-in command surface (
qcobjects [options] [command]):create <appname>,publish <appname>,generate-sw <appname>,launch <appname>;-V/--version,-h/--help; per-command help viaqcobjects-cli [command] --help.
Built-in commands, handlers, and libs (normative)¶
The framework ships a minimal set of built-ins that are always available without installing extra packages. All other capabilities enter via the keyword autoload contract (05-cli § Handlers/plugins/commands autoload, 16-addons).
- Built-in commands — two tiers, all shipping in-repo:
cli-main.tschoiceOption:create,publish(STUB — logs"publish is not yet implemented", ignores flags),upgrade-to-enterprise,generate-sw,launch(ignores its<appname>argument; serves CWD after a 5s delay). Sub-flags:--pwa,--amp,--php,--custom(takes a value ONLY oncreate; valueless onpublish),--tests(accepted but silently ignored — no-op).-
In-repo families via
cli-commands.ts:v-major/v-minor/v-patch/v-sync/ v-changelog(version),jira,publish:static,build:typescript(build:ts),build:esbuild(build:esb);upgrade-to-enterprisewired directly incli-main.ts.collabis NOT a commander family — it is a separate binary entry (qcobjects-collab.ts→collab-server.ts). -
Built-in handler records (
com.qcobjects.backend.microservice.static):defaultsettings.tsAPPENDS three static routes unconditionally (concat, not gated on emptybackend.routes) at every boot:^/QCObjects.js$→ coresrc/QCObjects.js,^/js/packages/QCObjects-SDK.js$→ SDKsrc/QCObjects-SDK.js,^/qcobjects-sdk/(.*)$→ SDK tree — all CORS*. The named microservice IS defined in this repo (src/backend/backend-microservice-static.ts): aBackendMicroservicesubclass servingredirect_totargets over HTTP/2 (respondWithFD) or legacy HTTP, with per-routesupported_methodsgating ("*"or case-insensitive list; absent = allowed). Use these routes for framework-asset serving instead of custom code. -
Core libraries (always present as peer dependencies):
qcobjects(core framework) andqcobjects-sdk(controllers, views, components, effects, cloud auth, i18n). These are NOT autoloaded — they are hard peer dependencies of every QCObjects app and CLI command. -
No other built-in handlers, libs, or commands exist. Any additional capability (payment handlers, email libs, admin panels, custom commands) MUST enter via the autoload keyword contract (
qcobjects-handler,qcobjects-lib,qcobjects-command,qcobjects-admin-lib) or explicitrequire/importin app code.
Custom templates (create --custom, normative)¶
Source: src/cli-main.ts (choiceOption.create, copyTemplate), pinned at
https://github.com/QCObjects/qcobjects-cli/blob/v2.5.158/src/cli-main.ts.
- Flags:
create <appname>resolves the template package by flag:--amp→qcobjects-ecommerce-amp,--pwa(or no flag) →qcobjectsnewapp,--php→qcobjectsnewphp,--custom <templateappname>→ any npm package name,--tests→ test suite.publishmirrors the same flags. - Flow (binding):
npm init -y→npm i --save-dev --legacy-peer-deps <template>→ adopt the template'spackage.json(renamed to<appname>, version reset to1.0.0via direct mutation,repositorycleared) →copyTemplate()from the installed package dir into the project (excludingpackage.json,node_modules,.DS_Store) →npm uninstall <template> --save+npm install qcobjects-cli+ fullnpm i --legacy-peer-deps+npm cache verify→ tail:qcobjects-createcert, fetch of.gitignorefrom GitHub,git init. - Key consequence: the template package is scaffolding only — installed, copied, then UNINSTALLED. Apps MUST NOT retain a runtime dependency on their template package; all cohesion lives in the copied files (see 06-app-structure).
- Authoring custom templates: any npm package with the app layout
(06-app-structure) + a
package.jsonworks as a--customtemplate (options.createCustomis used verbatim as the npm name — no naming constraint is enforced by tooling). Template packages MUST carry-templateas a SUFFIX by project convention (review-enforced, not tool-gated) —qcobjects-<name>-template(e.g.qcobjects-app-template) — and MUST declare the layout they stamp in their README. Kind-specific starters keep their kind infix:qcobjects-handler-<name>-template,qcobjects-lib-<name>-template,qcobjects-command-<name>-template. - Beyond apps — custom commands/libs/handlers:
copyTemplatecopies the whole package dir, so--customtemplates MAY stamp any package kind, not just apps: a command starter (class in acom.qcobjects.cli.commands.*package ending inCommandHandler, picked up bygetPluginCommandsList()and constructed with{switchCommander}), a lib starter (qcobjects-libkeyword), or a handler starter (qcobjects-handlerkeyword, microservice skeleton). The stamped package then follows the autoload contract (§ Handlers/plugins/commands autoload) and the add-on lifecycle (16-addons). Prefer stamping starters over documenting manual file creation.
Binaries (normative)¶
Single dispatcher: bin/qcobjects-cli.js serves all 10 package.json bin
aliases (qco, qcobjects, qcobjects-cli, qcobjects-server,
qcobjects-http-server, qcobjects-http2-server, qcobjects-gae-server,
qcobjects-shell, qcobjects-collab, qcobjects-createcert) via an
entryMap dispatch on the invoked basename. There are no sibling binaries.
Servers (normative)¶
- Implementations:
main-http-server.ts(HTTP legacy),main-http2-server.ts,main-http-gae-server.ts(App Engine), entered viaqcobjects-http-server.ts/qcobjects-http2-server.ts/qcobjects-gae-http-server.ts. NOTE: theqcobjects-http2-serverentrypoint picksHTTPServervsHTTP2ServeronuseLegacyHTTP— it is not always HTTP/2. - ALL behavior from
config.json: ports (serverPortHTTP/HTTPS),documentRoot,backend.routes, TLS via$config(domain)-derived filenames. process.env.PORToverrides the listen port ONLY on the legacy HTTP and GAE servers; the default HTTP/2start()ignoresPORT.- local
config.jsonat CLI root is gitignored dev-only (default devmode$ENV(DEVMODE,info)→info);$ENV(VAR)templates resolve indefaultsettings.ts. - Production recommendation: HTTP/2 server on Ubuntu 18.x+ with NodeJS 12.x+.
Commands & internals (normative)¶
- Framework: Commander —
SwitchCommander(cli-main.ts); in-repo families incli-commands*.ts(build-esbuild, build-typescript, jira, publish-static, version) re-exported viacli-commands.ts;upgrade-to-enterprisewired directly incli-main.ts;collabis a separate binary, not a family. - Modules import
qcobjectsand useInheritClass,Package(),Export(),CONFIG,logger,Component,Service(source convention, binding). - Plugin autodiscovery: see "Handlers/plugins/commands autoload" below.
- Entrypoints:
qcobjects-cli.ts,qcobjects-http{-2,}-server.ts,qcobjects-shell.ts,qcobjects-collab.ts; Deno viadeno.json+mod.ts. createcertgenerates self-signed local TLS; production MUST use external certs.
Handlers/plugins/commands autoload (normative)¶
Source: src/defaultsettings.ts (__load_default_settings__, runs at CLI boot;
__reset_settings__ re-runs it), pinned at
https://github.com/QCObjects/qcobjects-cli/blob/v2.5.158/src/defaultsettings.ts.
- Scan:
<projectPath>/package.jsondependencies(anddevDependenciesfor dev commands) are read; each installed package's ownpackage.jsonkeywordsare inspected (cached per package) forqcobjects-lib,qcobjects-handler,qcobjects-command. Matches areimport()ed viafindPackageNodePathresolution. - Flags: master
autodiscoverOR per-typeautodiscover_libs,autodiscover_handlers,autodiscover_commands. CLI built-in defaults turn ONautodiscover(defaultsettings.ts), so with shipped defaults ALL kinds — including libs — autoload with NO opt-in.autodiscover_libsonly matters when an app explicitly sets masterautodiscover:false: it re-enables libs alone. Production configs SHOULD set exactly the flags they need andfalsefor the rest (least privilege: every auto-imported package runs code at boot). - Order/concurrency: libs → handlers → commands → devCommands fire as four independent non-awaited chains — NO guaranteed order.
- Failure semantics: ALL FOUR chains attach warn-and-continue catches
(including commands) — a broken
qcobjects-commandlogs a warning and boot continues; nothing aborts boot. (The inner rethrow inloadCommandsis swallowed by the outer catch.) - Registry:
CONFIG.backendpublisheslibs,handlers,commands,devCommands— possibly still EMPTY at read time (fire-and-forget loaders). The rawdependencies/devDependenciesname lists are NEVER published (their memo closures return[]permanently — dead code). Introspection MUST read the four kind keys and tolerate emptiness, never re-scannode_modules. (backend.plugins = commands + devCommandsis also written, but races the fire-and-forget loaders — do not rely on it at boot.) - Publishing contract: a handler/plugin/command package MUST declare its
role in
package.jsonkeywords(qcobjects-handler,qcobjects-command, orqcobjects-lib) or it will never load, no matter the flags.
Synced semantic versioning (normative, from CLI README)¶
Version lives in the VERSION file; commands sync it to package.json/git:
v-patch(1.2.3→1.2.4),v-minor,v-major— same options:--sync-git/--git(commit+tag+push),--sync-npm/--npm(alsonpm version, implies--git),--commit-msg/-m.v-sync— adoptgit describe(latest tag) intoVERSION+package.json, commit, tag, push (-mdefaultSynced Version v<version>).v-changelog— changelog from annotated tags grouped by minor → stdout (v-changelog > CHANGELOG.md).- Typical flow:
v-patch --git --npm -m "msg"→ CI publishes →v-changelog. - Single-push rule:
syncGitpushes exactly once per release either way — with--npmthe tag is created bynpm version(manualgit tag -askipped), without--npmbygit tag -a— then onegit push && git push --tags. There is no double tag push in current code. GitHub Actions (tag-triggered publish) repos MUST still set"postversion": "git push"(branch only) to keep release pushes minimal. - Tests: jasmine 3.7, single
spec/testsSpec.tsassertingqcobjectsversion parity betweenpeerDependenciesanddevDependencies; SDK mocked viatsconfig.jasmine.jsonmapping;stopSpecOnExpectationFailure,failSpecWithNoExpectations,random:false. - Lint is permissive (
recommendedTypeCheckedwith coreno-unsafe-*,no-explicit-any,no-unused-varsoff); ignores cover**/*.js,spec/**/*,src/*.js,src/**/*.js,node_modules…. (Thetsconfig.jasmine.jsonSDK-mock mapping points atspec/mocks/qcobjects-sdk.mock.ts, which does NOT exist — dangling.)
Verification¶
- Fresh
qcobjects create myapp --pwa && qcobjects-serverserves the PWA shell on configured ports with zero manual edits. npm test(eslint + jasmine) green;tscdeclaration build emitspublic/types.v-patch --git(no--npm) produces exactly one tag push viagit tag -a(single push, no duplicate CI).