@skiesjs/cli
v0.1.1
Published
The explicit scaffolding CLI for Skies Node.js applications.
Readme
@skiesjs/cli
Transactional, explicit scaffolding for plain NodeNext TypeScript Skies applications. The binary is skies-node,
avoiding the .NET skies global tool and the unrelated npm package named skies.
Start an application
skies-node new invoices-api
cd invoices-api
npm install
npm run check
npm run build
npm startUse skies-node new . --name invoices-api to populate the current directory, or
skies-node new invoices-api --cwd ../services. The starter contains:
- a private package manifest and self-contained NodeNext build/test TypeScript configs;
- the recommended
@skiesjs/eslint-plugin-nodeflat config and a bounded Vitest config whoseSkiesProofReporterrequires proof metadata and writes.skies/foundation/vitest-receipt.json; - explicit Express/OpenAPI composition in
src/app.tsand runnablesrc/server.tsstartup; src/modules.tsplus a registered Health module, context, contract-backed slice, and co-located proof;lint,typecheck,test,doctor,build,check, andstartscripts.
The generated application exposes GET /health and GET /openapi/v1.json. It has no decorators, discovery,
dependency injection container, or generated runtime behavior.
Generate application code
Run these inside an application. PascalCase arguments become predictable kebab-case filenames.
# Structural generators
skies-node g module Billing
skies-node g context Billing
skies-node g slice Billing CreateInvoice --method post --route /invoices
skies-node g entity Billing Invoice
skies-node g crud Billing Invoice
skies-node g hub Billing InvoiceUpdates
# Domain and boundary generators
skies-node g error-code Billing InvoiceNotFound
skies-node g error-code Billing InvoiceNotFound --code billing.invoice_not_found
skies-node g value-object Billing InvoiceId
skies-node g page Billing Invoice
skies-node g storage
skies-node g storage --directory .data/files --base-url http://localhost:3000/files --route /files
skies-node g auth --issuer invoices --audience invoices-api
skies-node g auth:otp
skies-node g auth:oauth
skies-node g auth:emailAll g commands accept --cwd <application>, --root <source-root> (default src), and --dry-run.
Routes use OpenAPI syntax such as /invoices/{invoiceId} rather than Express :invoiceId syntax.
What each generator writes
g modulecreatessrc/modules/billing/billing.module.tsandbilling.ctx.md, then adds one namespace import and directBilling.map(...)call tosrc/modules.ts. A current two-argument registry produces Router/OpenAPI wiring; the legacy one-argument registry shape remains supported.g contextcreates only the missing module context.g slicecreatesslices/create-invoice.slice.tsand its exact sibling test. The slice owns a localdefineContract, explicitauth: "anonymous",kind: "app", Zod success schema,mapSlice, stable criterion, runnableResult, and proof. When the owning module uses the current Router/OpenAPI template, the same transaction adds its explicit import and map call so the workspace doctor stays green.g error-codecreatesbilling.errors.tswith adefineErrorCodesregistry member and a visibleErrors.businessRulefactory that consumes it. Change the explicit Errors factory when another canonical error category is appropriate. The default wire code isbilling.invoice_not_found.g value-objectcreatesvalues/invoice-id.errors.ts,invoice-id.ts, andinvoice-id.test.ts. Its smart constructor is authoritative;scalarCodecandscalarSchemaexpose the same string rule to domain and Zod/OpenAPI boundaries. Replace the scaffold rule and primitive metadata with the real invariant.g pagecreatespages/invoice.page.tsand its test, using corePage/mapPageplus an explicit Zod wire schema.g entitycreates an explicit Drizzle PostgreSQLpgTable, UUID primary key, organization owner, integer optimistic-concurrency version, timestamps, SQL migration, and executable table-shape test.g crudis one transaction: it reuses or creates that entity, then writes entity-specific Create/Get/List/Update/DeletedefineContract/mapSlicefiles, exact sibling tests and write journeys, a stable error registry and UUID scalar codec, concrete Drizzle queries usingpagePolicy/toPage/executeVersionedMutation, visible module dependency wiring, and manifest criteria/proofs. It never creates a generic repository.g hubcreates an explicit Socket.IOdefineSocketEventcontract andSocketIoAdapter.registermap. These are the actual@skiesjs/socketioAPI names (the package does not exportdefineSocketContractormapSocket).g storagecreatessrc/wiring/storage.tsand its test.LocalFileStorageconstruction andmapLocalFiles(app, files, ...)remain visible and removable.g authcreatessrc/wiring/auth.tsand its test. The caller must supply the signing secret at runtime; the generatedAccessTokensandrequireJwtrelationship is explicit.g auth:otp,g auth:oauth, andg auth:emailrequireg authfirst and refuse overwrites. They add focused provider-agnostic ports, digest-only or sealed state, stable errors, explicit expiry and atomic replay behavior, contract maps, and executable happy/sad proofs. No provider SDK or raw persisted OTP/code/link secret is generated.
The application needs the focused packages imported by a selected generator (@skiesjs/core, @skiesjs/openapi,
@skiesjs/express, @skiesjs/testing, Drizzle/PostgreSQL, Socket.IO, and, where relevant, the auth or storage
packages). new declares all of these so later generators typecheck without a manifest rewrite.
Transaction guarantees
Every command builds one FilePlan, validates every target before writing, and then applies it as an all-or-nothing
transaction. Plans reject normalized duplicates, traversal outside the application root, symlink escapes, unexpected
replacements, and every create collision. Replacements require byte-exact expected contents. A failure during staging
or rename rolls back created files, restored files, temporary files, backups, and newly empty directories.
--dry-run performs the same containment, symlink, structure, and collision preflight and prints the deterministic
ordered targets without writing. Generators deliberately refuse rerun overwrites; an identical second invocation fails
cleanly and leaves authored files byte-for-byte unchanged.
