@apso/cli 0.8.6 → 0.10.2

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.
Files changed (91) hide show
  1. package/README.md +221 -1653
  2. package/dist/commands/config.d.ts +13 -0
  3. package/dist/commands/config.js +114 -0
  4. package/dist/commands/deploy.d.ts +11 -0
  5. package/dist/commands/deploy.js +141 -0
  6. package/dist/commands/dev.d.ts +15 -0
  7. package/dist/commands/dev.js +95 -0
  8. package/dist/commands/generate.d.ts +12 -0
  9. package/dist/commands/generate.js +179 -0
  10. package/dist/commands/init.d.ts +16 -0
  11. package/dist/commands/init.js +313 -0
  12. package/dist/commands/link.d.ts +11 -0
  13. package/dist/commands/link.js +139 -0
  14. package/dist/commands/login.d.ts +22 -0
  15. package/dist/commands/login.js +345 -0
  16. package/dist/commands/logout.d.ts +9 -0
  17. package/dist/commands/logout.js +38 -0
  18. package/dist/commands/logs.d.ts +10 -0
  19. package/dist/commands/logs.js +64 -0
  20. package/dist/commands/mcp/serve.d.ts +11 -0
  21. package/dist/commands/mcp/serve.js +818 -0
  22. package/dist/commands/migrate.d.ts +11 -0
  23. package/dist/commands/migrate.js +137 -0
  24. package/dist/commands/open.d.ts +10 -0
  25. package/dist/commands/open.js +79 -0
  26. package/dist/commands/projects.d.ts +10 -0
  27. package/dist/commands/projects.js +106 -0
  28. package/dist/commands/schema/diff.d.ts +7 -0
  29. package/dist/commands/schema/diff.js +63 -0
  30. package/dist/commands/schema/pull.d.ts +9 -0
  31. package/dist/commands/schema/pull.js +81 -0
  32. package/dist/commands/schema/push.d.ts +9 -0
  33. package/dist/commands/schema/push.js +97 -0
  34. package/dist/commands/schema/validate.d.ts +9 -0
  35. package/dist/commands/schema/validate.js +90 -0
  36. package/dist/commands/server/new.d.ts +2 -19
  37. package/dist/commands/server/new.js +6 -192
  38. package/dist/commands/server/scaffold.d.ts +2 -25
  39. package/dist/commands/server/scaffold.js +6 -312
  40. package/dist/commands/status.d.ts +7 -0
  41. package/dist/commands/status.js +53 -0
  42. package/dist/commands/unlink.d.ts +9 -0
  43. package/dist/commands/unlink.js +47 -0
  44. package/dist/commands/whoami.d.ts +10 -0
  45. package/dist/commands/whoami.js +97 -0
  46. package/dist/lib/api/client.js +1 -1
  47. package/dist/lib/api/services.js +3 -3
  48. package/dist/lib/api/types.d.ts +9 -2
  49. package/dist/lib/apsorc-parser.d.ts +0 -19
  50. package/dist/lib/apsorc-parser.js +2 -73
  51. package/dist/lib/guards.d.ts +3 -20
  52. package/dist/lib/guards.js +1 -113
  53. package/dist/lib/index.d.ts +1 -9
  54. package/dist/lib/index.js +1 -18
  55. package/dist/lib/migrate/entity-generator.d.ts +14 -0
  56. package/dist/lib/migrate/entity-generator.js +7 -0
  57. package/dist/lib/migrate/sandbox.d.ts +42 -0
  58. package/dist/lib/migrate/sandbox.js +337 -0
  59. package/dist/lib/migrate/snapshot.d.ts +80 -0
  60. package/dist/lib/migrate/snapshot.js +100 -0
  61. package/dist/lib/templates/python/models/model-col-datetime.eta +1 -1
  62. package/dist/lib/templates/python/models/model-col-string.eta +1 -1
  63. package/dist/lib/templates/python/models/model-col-text.eta +1 -1
  64. package/dist/lib/templates/python/models/model-col-varchar.eta +1 -1
  65. package/dist/lib/utils/field.d.ts +12 -0
  66. package/dist/lib/utils/field.js +80 -1
  67. package/dist/lib/utils/schema-convert.d.ts +71 -0
  68. package/dist/lib/utils/schema-convert.js +169 -0
  69. package/dist/lib/utils/spinner.d.ts +19 -0
  70. package/dist/lib/utils/spinner.js +87 -0
  71. package/dist/lib/utils/template.d.ts +12 -0
  72. package/dist/lib/utils/template.js +49 -0
  73. package/npm-shrinkwrap.json +18118 -0
  74. package/oclif.manifest.json +547 -41
  75. package/package.json +26 -5
  76. package/dist/lib/controller.d.ts +0 -10
  77. package/dist/lib/controller.js +0 -99
  78. package/dist/lib/dto.d.ts +0 -16
  79. package/dist/lib/dto.js +0 -92
  80. package/dist/lib/entity.d.ts +0 -19
  81. package/dist/lib/entity.js +0 -56
  82. package/dist/lib/enums.d.ts +0 -6
  83. package/dist/lib/enums.js +0 -28
  84. package/dist/lib/gql-dto.d.ts +0 -3
  85. package/dist/lib/gql-dto.js +0 -39
  86. package/dist/lib/index-module.d.ts +0 -13
  87. package/dist/lib/index-module.js +0 -26
  88. package/dist/lib/module.d.ts +0 -13
  89. package/dist/lib/module.js +0 -42
  90. package/dist/lib/service.d.ts +0 -2
  91. package/dist/lib/service.js +0 -61
package/README.md CHANGED
@@ -1,1786 +1,354 @@
1
1
  # Apso CLI
2
2
 
3
- Generate production-ready NestJS backends from schema definitions.
3
+ Define a schema. Get a production API. Keep the code.
4
4
 
5
- ## Quick Start
5
+ Apso generates production-ready backend services from a JSON schema file. You get real framework code (NestJS, FastAPI, or Gin) that you own, run anywhere, and extend with standard patterns. For the complete documentation, see [Apso Docs](https://docs.apso.ai).
6
6
 
7
- ```bash
8
- # 1. Install CLI
9
- npm install -g @apso/apso-cli
10
-
11
- # 2. Create new project
12
- apso server new --name myapp
13
-
14
- # 3. Edit .apsorc to define your schema (see examples below)
15
-
16
- # 4. Generate code
17
- apso server scaffold
18
-
19
- # 5. Start database & provision schema
20
- npm run compose && npm run provision
21
-
22
- # 6. Run development server
23
- npm run start:dev
24
- ```
25
-
26
- ---
27
-
28
- ## Table of Contents
29
-
30
- - [Quick Start](#quick-start)
31
- - [Usage](#usage)
32
- - [Important: Never Modify Autogen Files](#-important-never-add-custom-code-to-autogen-files)
33
- - [Common First-Time Mistakes](#️-common-first-time-mistakes)
34
- - [Local Development](#local-development)
35
- - [Populating an .apsorc File](#populating-an-apsorc-file)
36
- - [Auto-Generated Code Reference](#auto-generated-code-reference)
37
- - [Relationships](#relationships)
38
- - [Authentication (Bring Your Own Auth)](#authentication-bring-your-own-auth)
39
- - [Data Scoping (Multi-Tenant Isolation)](#data-scoping-multi-tenant-isolation)
40
- - [Authentication + Scoping: Working Together](#authentication--scoping-working-together)
41
- - [Schema Reference](#schema-reference)
42
- - [Debugging](#debugging)
43
- - [Commands](#commands)
44
-
45
- ---
46
-
47
- # Usage
48
-
49
- Follow these steps to create and run a new APSO server project:
50
-
51
- 1. **Install the CLI globally:**
52
- ```sh
53
- npm install -g @apso/apso-cli
54
- ```
55
-
56
- 2. **Initialize a new server project:**
57
- ```sh
58
- apso server new --name <PROJECT_NAME>
59
- ```
60
- This creates a new project folder with the necessary boilerplate.
61
-
62
- 3. **Define your database schema:**
63
- Edit the `.apsorc` file in your new project folder to describe your entities and relationships. See the [Populating an .apsorc File](#populating-an-apsorc-file) section for details and examples.
64
-
65
- 4. **Generate code and database entities:**
66
- ```sh
67
- apso server scaffold
68
- ```
69
- This command generates all relevant modules and entity code based on your `.apsorc` file.
70
-
71
- 5. **Configure your database connection:**
72
- Update your project's `.env` file with the database credentials you want to use.
73
-
74
- 6. **Start the local Postgres instance (Docker):**
75
- ```sh
76
- npm run compose
77
- ```
78
- This command uses Docker Compose to start a local Postgres database instance.
79
-
80
- 7. **Provision your schema/database:**
81
- ```sh
82
- npm run provision
83
- ```
84
- This sets up your new schema instance in the database.
85
-
86
- 8. **(Optional) Enable automatic model sync for local development:**
87
- If you want to skip manual migrations and always sync your models with the database (useful for rapid prototyping or starting from scratch), set the following in your `.env` file:
88
- ```env
89
- DATABASE_SYNC=true
90
- ```
91
- With this setting, your models will be automatically synced to the database on startup.
92
-
93
- > For more details on configuring your schema, see the [Populating an .apsorc File](#populating-an-apsorc-file) section below.
94
-
95
- ---
96
-
97
- ## 📢 Important: Never Add Custom Code to Autogen Files
98
-
99
- Apso CLI generates all files in the `autogen/` directory automatically.
100
- **Any changes you make directly to these files will be overwritten the next time you run Apso CLI.**
101
- To keep your custom logic safe and maintainable, always use the `extensions/` directory for any customizations.
102
-
103
- ### How to Extend Apso-Generated Entities
104
-
105
- #### 1. **Never modify files in `autogen/`**
106
-
107
- - All files in `src/autogen/` are managed by Apso CLI.
108
- - These include entities, services, controllers, DTOs, and modules.
109
- - **Do not add custom endpoints, business logic, or integrations here.**
110
-
111
- #### 2. **Add custom logic in `extensions/`**
112
-
113
- - For each entity you want to extend, create a corresponding folder in `src/extensions/[[EntityName]]/`.
114
- - Add your custom service, controller, and DTOs here.
115
- - You can inject and extend the autogen service in your extension service.
116
-
117
- #### 3. **Example Directory Structure**
118
-
119
- ```
120
- src/
121
- autogen/
122
- LambdaDeployment/
123
- LambdaDeployment.service.ts # DO NOT MODIFY
124
- LambdaDeployment.controller.ts
125
- ...
126
- extensions/
127
- LambdaDeployment/
128
- LambdaDeployment.service.ts # Add custom logic here
129
- LambdaDeployment.controller.ts
130
- ...
131
- ```
132
-
133
- #### 4. **Example: Extending LambdaDeployment**
7
+ ## Install
134
8
 
135
- **Custom Service:**
136
- ```typescript
137
- // src/extensions/LambdaDeployment/LambdaDeployment.service.ts
138
- import { Injectable } from '@nestjs/common';
139
- import { LambdaDeploymentService as AutogenLambdaDeploymentService } from '../../autogen/LambdaDeployment/LambdaDeployment.service';
9
+ **Homebrew** (macOS / Linux)
140
10
 
141
- @Injectable()
142
- export class LambdaDeploymentService extends AutogenLambdaDeploymentService {
143
- // Add your custom methods here
144
- async deployWithLocalService(...) { ... }
145
- }
146
- ```
147
-
148
- **Custom Controller:**
149
- ```typescript
150
- // src/extensions/LambdaDeployment/LambdaDeployment.controller.ts
151
- import { Controller } from '@nestjs/common';
152
- import { LambdaDeploymentService } from './LambdaDeployment.service';
153
-
154
- @Controller('lambda-deployment')
155
- export class LambdaDeploymentController {
156
- constructor(private readonly lambdaDeploymentService: LambdaDeploymentService) {}
157
-
158
- // Add custom endpoints here
159
- }
11
+ ```bash
12
+ brew tap apsoai/tap
13
+ brew install apso
160
14
  ```
161
15
 
162
- #### 5. **Why This Matters**
163
-
164
- - Keeps your custom business logic safe from being overwritten.
165
- - Makes it easy to regenerate your API as your data model evolves.
166
- - Maintains a clean separation between generated code and your application logic.
167
-
168
- #### 6. **Best Practices**
169
-
170
- - Only use the autogen files for base CRUD and entity logic.
171
- - Place all custom endpoints, integrations, and business logic in the `extensions/` directory.
172
- - If you need to override or extend a method, subclass the autogen service in your extension service.
173
-
174
- ---
16
+ **npm**
175
17
 
176
- **Summary:**
177
- > Always put your custom code in `src/extensions/[[EntityName]]/`.
178
- > Never modify files in `src/autogen/`.
179
- > This ensures your work is safe and your project remains maintainable as you evolve your data model with Apso CLI.
180
-
181
- ---
182
-
183
- ## ⚠️ Common First-Time Mistakes
184
-
185
- ### 1. Modifying `autogen/` Files
186
- **Problem:** Changes get overwritten on next scaffold
187
- **Solution:** Always use `extensions/` directory for custom code
188
-
189
- ### 2. Defining Both Sides of Relationships
190
- **Problem:** Duplicate properties and TypeScript errors
191
- **Solution:** Define relationships **once** - Apso auto-generates the inverse side
192
-
193
- Example:
194
- ```json
195
- // ❌ WRONG - Creates conflicts
196
- { "from": "User", "to": "Workspace", "type": "OneToMany" }
197
- { "from": "Workspace", "to": "User", "type": "ManyToOne" }
198
-
199
- // ✅ CORRECT - Define one side only
200
- { "from": "Workspace", "to": "User", "type": "ManyToOne", "to_name": "owner" }
18
+ ```bash
19
+ npm install -g @apso/cli
201
20
  ```
202
21
 
203
- ### 3. Skipping Database Provisioning
204
- **Problem:** Server fails to start with missing table errors
205
- **Solution:** Always run `npm run provision` after scaffold
206
-
207
- ### 4. Wrong .apsorc Version
208
- **Problem:** Schema doesn't generate correctly
209
- **Solution:** Ensure `"version": 2` at top of .apsorc
210
-
211
- ### 5. Forgetting to Start Docker
212
- **Problem:** Database connection refused errors
213
- **Solution:** Run `npm run compose` before starting server
22
+ Requires Node.js 18.0 or higher.
214
23
 
215
- ---
24
+ ## Connect
216
25
 
217
- # Local Development
26
+ Authenticate with the Apso platform:
218
27
 
219
- Clone apso-cli on your machine. Navigate to the repo in your code editor and run the below commands
220
-
221
- ```sh-session
222
- npm install
223
- npm run build && npm link
224
- ```
225
-
226
- This will build the apso cli and make it available for use globally on your machine. Now make a new directory where you want to setup a new backend service. Run the below command in order to create a new service boilerplate. This will clone the apso-service-template from github.
227
-
228
- Incase you face permission denied issue make sure that you have SSH key added in github and your local machine. Follow this [link](https://docs.github.com/en/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent) for SSH keys generation.
229
-
230
- ```sh-session
231
- apso server new -n <project-name>
28
+ ```bash
29
+ apso login
232
30
  ```
233
31
 
234
- Then navigate to the newly created service and update the apsorc file according to your project entities and relations.
235
-
236
- Sample of apsorc files for both v1 and v2 are given in apso-cli code at "test/apsorc-json" so you can check it out in order to make sure that your apsorc file follows the right pattern.
237
-
238
- For detailed examples of configuring complex relationships, especially ManyToMany and self-referencing patterns, see the [Relationship Configuration Use Cases](./UseCases.md).
32
+ This opens a browser window for OAuth authentication. For CI/CD environments, use a token:
239
33
 
240
- You can also provide the key "apiType" in the apsorc file e.g (Rest, Graphql) incase you want to generate the GraphQL backend by default it would be REST.
241
-
242
- Install the npm modules before continuing further.
243
-
244
- Now we will run the scaffold command which will generate the all the relevant modules for us.
245
-
246
- ```sh-session
247
- apso server scaffold
248
- ```
249
-
250
- # Populating an .apsorc File
251
-
252
- The `.apsorc` file defines your domain model, including entities and their relationships, for APSO code generation. It is required for scaffolding your backend service.
253
-
254
- ## How to Create and Populate `.apsorc`
255
-
256
- 1. **Location**: Place the `.apsorc` file in the root of your service directory.
257
- 2. **Version**: Set the `version` property to `2` for the latest schema.
258
- 3. **Entities**: Define each domain entity, its fields, and any unique constraints.
259
- 4. **Relationships**: Specify how entities relate (e.g., OneToMany, ManyToOne, etc.).
260
- 5. **rootFolder**: Set the folder where generated code will be placed (e.g., `src`).
261
-
262
- > **Tip:** You can find sample `.apsorc` files in `apso-cli/test/apsorc-json/` for both v1 and v2 formats.
263
-
264
- ### Example `.apsorc` v2 File
265
-
266
- ```json
267
- {
268
- "version": 2,
269
- "rootFolder": "src",
270
- "relationships": [
271
- { "from": "User", "to": "WorkspaceUser", "type": "OneToMany", "nullable": true },
272
- { "from": "Workspace", "to": "WorkspaceUser", "type": "OneToMany" },
273
- { "from": "Workspace", "to": "Application", "type": "OneToMany", "index": true },
274
- { "from": "Application", "to": "ApplicationService", "type": "OneToMany" },
275
- { "from": "Application", "to": "User", "type": "ManyToOne", "to_name": "owner" },
276
- { "from": "ApplicationService", "to": "ApplicationServiceApiKey", "type": "OneToMany" },
277
- { "from": "ApplicationService", "to": "ApplicationServiceMetric", "type": "OneToMany" },
278
- { "from": "ApplicationService", "to": "InfrastructureStack", "type": "ManyToOne", "to_name": "networkStack", "nullable": true },
279
- { "from": "ApplicationService", "to": "InfrastructureStack", "type": "ManyToOne", "to_name": "databaseStack", "nullable": true },
280
- { "from": "InfrastructureStack", "to": "InfrastructureStack", "type": "ManyToOne", "to_name": "networkStack", "nullable": true }
281
- ],
282
- "entities": [
283
- {
284
- "name": "User",
285
- "created_at": true,
286
- "updated_at": true,
287
- "fields": [
288
- { "name": "cognito_id", "type": "text", "unique": true },
289
- { "name": "email", "type": "text", "length": 255, "is_email": true },
290
- { "name": "fullName", "type": "text", "nullable": true }
291
- ]
292
- },
293
- {
294
- "name": "Workspace",
295
- "created_at": true,
296
- "updated_at": true,
297
- "fields": [
298
- { "name": "name", "type": "text" }
299
- ]
300
- },
301
- {
302
- "name": "WorkspaceUser",
303
- "created_at": true,
304
- "updated_at": true,
305
- "fields": [
306
- { "name": "email", "type": "text", "length": 255, "is_email": true },
307
- { "name": "invite_code", "type": "text", "length": 64 },
308
- { "name": "role", "type": "enum", "values": ["User", "Admin"], "default": "Admin" },
309
- { "name": "status", "type": "enum", "values": ["Active", "Invited", "Inactive", "Deleted"] },
310
- { "name": "activeAt", "type": "date", "nullable": true }
311
- ]
312
- },
313
- {
314
- "name": "Application",
315
- "created_at": true,
316
- "updated_at": true,
317
- "fields": [
318
- { "name": "name", "type": "text" },
319
- { "name": "status", "type": "enum", "values": ["Active", "Deleted"] }
320
- ]
321
- }
322
- // ... more entities as needed ...
323
- ]
324
- }
34
+ ```bash
35
+ apso login --token <api-token>
325
36
  ```
326
37
 
327
- For a full example, see [`apso-cli/test/apsorc-json/apsorc.v2.json`](./test/apsorc-json/apsorc.v2.json).
328
-
329
- ## Auto-Generated Code Reference
330
-
331
- This section details the code that Apso CLI automatically generates from your `.apsorc` configuration, including fields, TypeORM mappings, validation, and naming conventions. Understanding these rules will help you predict and control your generated backend code.
332
-
333
- ### 1. Auto-Generated Fields
334
-
335
- #### Primary Keys
336
- - Every entity automatically receives an `id` field, even if not defined in the `fields` array.
337
- - Decorated as `@PrimaryGeneratedColumn()` (auto-incrementing integer).
338
- - Type: `number`.
339
-
340
- ```typescript
341
- @PrimaryGeneratedColumn()
342
- id: number;
343
- ```
344
-
345
- #### Timestamps
346
- - If `"created_at": true` is set on the entity, generates:
347
- ```typescript
348
- @CreateDateColumn()
349
- created_at: Date;
350
- ```
351
- - If `"updated_at": true` is set on the entity, generates:
352
- ```typescript
353
- @UpdateDateColumn()
354
- updated_at: Date;
355
- ```
356
- - These are in addition to any fields you define.
357
-
358
- #### Foreign Key Fields
359
- - For each relationship, Apso generates the foreign key column and TypeORM decorators.
360
- - Example:
361
- ```json
362
- { "from": "Workspace", "to": "User", "type": "ManyToOne", "to_name": "owner" }
363
- ```
364
- Generates:
365
- ```typescript
366
- @ManyToOne(() => User)
367
- @JoinColumn({ name: 'ownerId' })
368
- owner: User;
369
-
370
- @Column({ type: 'integer' })
371
- ownerId: number;
372
- ```
373
-
374
- ### 2. Field Type Mapping Table
375
-
376
- > **⚠️ PostGIS Requirements:** The following spatial data types require the PostGIS extension to be installed in your PostgreSQL database:
377
- > ```sql
378
- > CREATE EXTENSION IF NOT EXISTS postgis;
379
- > ```
380
- > Ensure PostGIS is installed before using any of the spatial data types listed below.
381
-
382
- | Apso Field Type | TypeORM Column Decorator | Auto-Applied Validation | Notes |
383
- |-----------------|-----------------------------------------|----------------------------------------|---------------------------------------|
384
- | `text` | `@Column({ type: 'text' })` | `@IsString()`, `@IsNotEmpty()` | With `length`: `@MaxLength(n)` |
385
- | `json` | `@Column('jsonb')` | None | Uses JSONB in PostgreSQL |
386
- | `enum` | `@Column({ type: 'enum', enum: [...] })`| None | Values array becomes enum |
387
- | `boolean` | `@Column({ type: 'boolean' })` | `@IsBoolean()` | Default values supported |
388
- | `integer` | `@Column({ type: 'integer' })` | `@IsNumber()` | |
389
- | `decimal` | `@Column({ type: 'decimal', precision: n, scale: m })` | `@IsNumber()` | Precision/scale supported |
390
- | `numeric` | `@Column({ type: 'numeric', precision: n, scale: m })` | `@IsNumber()` | Precision/scale supported |
391
- | `timestamp` | `@Column({ type: 'timestamp' })` | None | |
392
- | `point` | `@Column({ type: 'point' })` | None | PostGIS point geometry ⚠️ Requires PostGIS |
393
- | `linestring` | `@Column({ type: 'linestring' })` | None | PostGIS line string geometry ⚠️ Requires PostGIS |
394
- | `polygon` | `@Column({ type: 'polygon' })` | None | PostGIS polygon geometry ⚠️ Requires PostGIS |
395
- | `multipoint` | `@Column({ type: 'multipoint' })` | None | PostGIS multi-point geometry ⚠️ Requires PostGIS |
396
- | `multilinestring` | `@Column({ type: 'multilinestring' })` | None | PostGIS multi-line string geometry ⚠️ Requires PostGIS |
397
- | `multipolygon` | `@Column({ type: 'multipolygon' })` | None | PostGIS multi-polygon geometry ⚠️ Requires PostGIS |
398
- | `geometry` | `@Column({ type: 'geometry' })` | None | PostGIS generic geometry type ⚠️ Requires PostGIS |
399
- | `geography` | `@Column({ type: 'geography' })` | None | PostGIS geography type ⚠️ Requires PostGIS |
400
- | `geometrycollection` | `@Column({ type: 'geometrycollection' })` | None | PostGIS geometry collection ⚠️ Requires PostGIS |
401
-
402
- #### Decimal/Numeric Field Examples
403
-
404
- **Basic decimal field:**
405
- ```json
406
- {
407
- "name": "price",
408
- "type": "decimal",
409
- "precision": 10,
410
- "scale": 2,
411
- "default": 0,
412
- "nullable": false
413
- }
414
- ```
415
- Generates:
416
- ```typescript
417
- @Column({ "type": "decimal", precision: 10, scale: 2, default: 0 })
418
- price: number;
419
- ```
38
+ ## Quick start
420
39
 
421
- **Numeric field with custom precision:**
422
- ```json
423
- {
424
- "name": "bandwidth_gb",
425
- "type": "numeric",
426
- "precision": 10,
427
- "scale": 3,
428
- "default": 0,
429
- "nullable": false
430
- }
431
- ```
432
- Generates:
433
- ```typescript
434
- @Column({ "type": "numeric", precision: 10, scale: 3, default: 0 })
435
- bandwidth_gb: number;
436
- ```
40
+ ```bash
41
+ # Create a new project
42
+ apso init --name my-app --language typescript
437
43
 
438
- #### PostGIS Field Examples
439
-
440
- > **📋 PostGIS Setup Required:** Before using any PostGIS data types, ensure your PostgreSQL database has the PostGIS extension installed:
441
- > ```sql
442
- > -- Install PostGIS extension
443
- > CREATE EXTENSION IF NOT EXISTS postgis;
444
- >
445
- > -- Verify installation
446
- > SELECT PostGIS_Version();
447
- > ```
448
- >
449
- > **Deployment Note:** When deploying applications with PostGIS fields, ensure the PostGIS extension is available in your production database environment.
450
-
451
- **Point geometry field:**
452
- ```json
453
- {
454
- "name": "location",
455
- "type": "point",
456
- "nullable": true
457
- }
458
- ```
459
- Generates:
460
- ```typescript
461
- @Column({
462
- "type": "point",
463
- transformer: {
464
- to: (point: {x: number, y: number} | null) => {
465
- if (!point) return null;
466
- return `(${point.x},${point.y})`;
467
- },
468
- from: (pgPoint: string | null) => {
469
- if (!pgPoint) return null;
470
- const [x, y] = pgPoint.substring(1, pgPoint.length - 1).split(',');
471
- return { x: parseFloat(x), y: parseFloat(y) };
472
- }
473
- },
474
- nullable: true
475
- })
476
- location: { x: number, y: number };
477
- ```
44
+ # Edit .apsorc to define your schema
478
45
 
479
- **Polygon geometry field:**
480
- ```json
481
- {
482
- "name": "boundary",
483
- "type": "polygon",
484
- "nullable": true
485
- }
486
- ```
487
- Generates:
488
- ```typescript
489
- @Column({
490
- "type": "polygon",
491
- transformer: {
492
- to: (polygon: { coordinates: Array<Array<{x: number, y: number}>> } | null) => {
493
- if (!polygon) return null;
494
- const rings = polygon.coordinates.map(ring => {
495
- const coords = ring.map(coord => `${coord.x} ${coord.y}`).join(',');
496
- return `(${coords})`;
497
- });
498
- return `POLYGON(${rings.join(',')})`;
499
- },
500
- from: (pgPolygon: string | null) => {
501
- if (!pgPolygon) return null;
502
- const match = pgPolygon.match(/POLYGON\((.+)\)/);
503
- if (!match) return null;
504
- const rings = match[1].split('),(').map(ring => {
505
- const cleanRing = ring.replace(/[()]/g, '');
506
- const coords = cleanRing.split(',').map(coord => {
507
- const [x, y] = coord.trim().split(' ');
508
- return { x: parseFloat(x), y: parseFloat(y) };
509
- });
510
- return coords;
511
- });
512
- return { coordinates: rings };
513
- }
514
- },
515
- nullable: true
516
- })
517
- boundary: { coordinates: Array<Array<{ x: number, y: number }>> };
518
- ```
46
+ # Generate code from schema
47
+ apso generate
519
48
 
520
- **Generic geometry field:**
521
- ```json
522
- {
523
- "name": "shape",
524
- "type": "geometry",
525
- "nullable": true
526
- }
49
+ # Start Postgres and run the API
50
+ apso dev
527
51
  ```
528
- Generates:
529
- ```typescript
530
- @Column({
531
- "type": "geometry",
532
- transformer: {
533
- to: (geometry: any) => {
534
- if (!geometry) return null;
535
- return geometry;
536
- },
537
- from: (pgGeometry: any) => {
538
- if (!pgGeometry) return null;
539
- return pgGeometry;
540
- }
541
- },
542
- nullable: true
543
- })
544
- shape: any;
545
- ```
546
-
547
- ### 3. Validation Rules Documentation
548
52
 
549
- Field properties in `.apsorc` map to validation decorators as follows:
53
+ Your API is live at `http://localhost:3000` with Swagger docs at `/api`. The generated code lives in `src/autogen/` and is standard NestJS with TypeORM -- no Apso runtime dependency.
550
54
 
551
- - `"unique": true` → `@Column({ unique: true })`
552
- - `"nullable": true` → `@Column({ nullable: true })` and `@IsOptional()`
553
- - `"is_email": true` → `@IsEmail()`
554
- - `"length": 255` → `@MaxLength(255)`
555
- - `"precision": 10` → `@Column({ precision: 10 })` (for decimal/numeric types)
556
- - `"scale": 2` → `@Column({ scale: 2 })` (for decimal/numeric types)
557
- - `"required": false` → `@IsOptional()` (for CREATE group)
55
+ ## Commands
558
56
 
559
- ### 4. Relationship Generation Rules
57
+ | Command | Subcommands | Description |
58
+ |---------|------------|-------------|
59
+ | [init](#apso-init) | | Create a new project |
60
+ | [generate](#apso-generate) | | Generate code from `.apsorc` schema |
61
+ | [dev](#apso-dev) | | Start local dev server via Docker Compose |
62
+ | [migrate](#apso-migrate) | | Test schema migrations locally with PGlite |
63
+ | [deploy](#apso-deploy) | | Deploy to Apso platform |
64
+ | [login](#apso-login) | | Authenticate with Apso |
65
+ | [logout](#apso-logout) | | Clear stored credentials |
66
+ | [whoami](#apso-whoami) | | Show current user |
67
+ | [link](#apso-link) | | Link project to a platform service |
68
+ | [unlink](#apso-unlink) | | Remove platform link |
69
+ | [status](#apso-status) | | Show service and build status |
70
+ | [logs](#apso-logs) | | View build logs |
71
+ | [open](#apso-open) | | Open service dashboard in browser |
72
+ | [projects](#apso-projects) | | List services in a workspace |
73
+ | [config](#apso-config) | `get`, `set`, `reset` | View or modify CLI configuration |
74
+ | [schema](#apso-schema) | `diff`, `push`, `pull`, `validate` | Manage schema sync with platform |
560
75
 
561
- #### OneToMany Relationships
76
+ ## Command reference
562
77
 
563
- ```json
564
- { "from": "User", "to": "Workspace", "type": "OneToMany", "to_name": "ownedWorkspaces" }
565
- ```
566
- Generates:
567
- ```typescript
568
- @OneToMany(() => Workspace, (workspace) => workspace.user)
569
- ownedWorkspaces: Workspace[];
570
- ```
78
+ ### `apso init`
571
79
 
572
- #### ManyToOne Relationships
80
+ Create a new Apso project from a language-specific template.
573
81
 
574
- ```json
575
- { "from": "Workspace", "to": "User", "type": "ManyToOne", "to_name": "owner" }
576
- ```
577
- Generates:
578
- ```typescript
579
- @ManyToOne(() => User, (user) => user.ownedWorkspaces)
580
- @JoinColumn({ name: 'userId' })
581
- owner: User;
582
-
583
- @Column({ type: 'integer' })
584
- userId: number;
82
+ ```bash
83
+ apso init
84
+ apso init --name my-app --language typescript
85
+ apso init --name my-app --language python --skip-platform
585
86
  ```
586
87
 
587
- #### Foreign Key Naming
588
- - Foreign key columns use camelCase + `Id` (e.g., `userId`).
589
- - Join column names match the foreign key column name.
590
-
591
- ### 5. Entity Naming Conventions
592
- - Entity class names remain as defined in `.apsorc` (e.g., `User`).
593
- - Table names are lowercased (e.g., `user`).
594
- - Foreign key columns use camelCase + `Id` (e.g., `userId`).
595
- - Join column names match the foreign key column name.
596
-
597
- ### 6. Version 2 Format Differences
598
-
599
- Version 2 of the `.apsorc` format introduces several enhancements:
600
- - Separate `relationships` and `entities` arrays.
601
- - `created_at`/`updated_at` as boolean flags at the entity level.
602
- - Enhanced field types: `json`, `enum`, `timestamp`, `decimal`, `numeric`.
603
- - `to_name` property in relationships for custom property names.
604
- - `precision` and `scale` properties for decimal/numeric fields.
605
-
606
- Refer to the [example v2 file](#example-apsorc-v2-file) for usage.
607
-
608
- ## Relationships
88
+ | Option | Description | Default |
89
+ |--------|-------------|---------|
90
+ | `-n, --name` | Project name | _(prompted)_ |
91
+ | `-l, --language` | Target language (`typescript`, `python`, `go`) | _(prompted)_ |
92
+ | `--skip-platform` | Skip platform linking (offline mode) | `false` |
609
93
 
610
- ### 1. Relationships Best Practices
94
+ When authenticated, `apso init` lets you create a new project or clone an existing one from the platform.
611
95
 
612
- > **Important:** Only define ONE side of a relationship in your `.apsorc` file. Apso CLI will auto-generate the inverse side for you. Defining both sides leads to duplicate properties, compilation errors, and entity conflicts.
96
+ ### `apso generate`
613
97
 
614
- #### DO/DON'T Examples
98
+ Generate backend code from the `.apsorc` schema file.
615
99
 
616
- ```json
617
- // ❌ WRONG - Creates Conflicts:
618
- { "from": "User", "to": "Workspace", "type": "OneToMany" },
619
- { "from": "Workspace", "to": "User", "type": "ManyToOne" }
620
-
621
- // ✅ CORRECT - One Side Only:
622
- { "from": "Workspace", "to": "User", "type": "ManyToOne", "to_name": "owner" }
623
- ```
624
-
625
- - **DO:** Define only the direction that best fits your domain model.
626
- - **DON'T:** Define both directions for the same relationship.
627
- - **Why?** Apso CLI auto-generates the inverse property and decorators. Duplicating both sides causes duplicate property and foreign key generation.
628
-
629
- ### 2. Auto-Generated Properties Documentation
630
-
631
- #### ManyToOne Relationships
632
- - Generates:
633
- - Entity property with `@ManyToOne`, `@JoinColumn`, and `@Column` decorators
634
- - Foreign key column: `{relationName}Id`
635
- - Example:
636
- ```typescript
637
- @ManyToOne(() => User)
638
- @JoinColumn({ name: 'ownerId' })
639
- owner: User;
640
-
641
- @Column({ type: 'integer' })
642
- ownerId: number;
643
- ```
644
-
645
- #### OneToMany Relationships
646
- - Generates:
647
- - Array property with `@OneToMany` decorator
648
- - Inverse mapping to the ManyToOne property
649
- - Example:
650
- ```typescript
651
- @OneToMany(() => Workspace, (workspace) => workspace.owner)
652
- workspaces: Workspace[];
653
- ```
654
-
655
- #### ManyToMany Relationships
656
- - Generates:
657
- - Join table and array properties on both entities
658
- - Example:
659
- ```typescript
660
- @ManyToMany(() => Tag, (tag) => tag.posts)
661
- @JoinTable()
662
- tags: Tag[];
663
- ```
664
- - **Warning:** If you define both a ManyToMany relationship and an explicit join entity, you may get duplicate join tables and properties. Prefer one approach.
665
-
666
- ### 3. Common Pitfalls and Solutions
667
-
668
- #### Duplicate Identifier Errors
669
- - **Cause:** Defining both sides of a relationship (bidirectional definitions)
670
- - **Solution:** Remove one side; only define the relationship once.
671
-
672
- #### "Property does not exist" Errors
673
- - **Cause:** Referencing an inverse property that was not generated (e.g., using a `to_name` that doesn't match)
674
- - **Solution:** Only use explicitly defined property names; check generated code for actual property names.
675
-
676
- #### "Multiple properties with same name" Errors
677
- - **Cause:** Complex or circular relationship chains, or duplicate relationship definitions
678
- - **Solution:** Simplify relationships, avoid deep nesting, and ensure each relationship is defined only once.
679
-
680
- #### ManyToMany + Explicit Join Entity Conflicts
681
- - **Cause:** Defining both a ManyToMany and a join entity for the same relationship
682
- - **Solution:** Use either a ManyToMany or a join entity, not both.
683
-
684
- #### Circular Eager Loading Issues
685
- - **Cause:** Deeply nested or circular relationships
686
- - **Solution:** Limit eager loading, use lazy relations, and avoid unnecessary deep nesting in your model.
687
-
688
- ### 4. Relationship Patterns
689
-
690
- #### Multi-Tenant Architecture
691
- ```json
692
- { "from": "Resource", "to": "Workspace", "type": "ManyToOne", "to_name": "workspace" },
693
- { "from": "Resource", "to": "User", "type": "ManyToOne", "to_name": "createdBy" }
694
- ```
695
-
696
- #### Audit Trails, Deployment History, Optional Relationships
697
- ```json
698
- { "from": "Deployment", "to": "User", "type": "ManyToOne", "to_name": "deployedBy", "nullable": true },
699
- { "from": "AuditLog", "to": "Resource", "type": "ManyToOne", "to_name": "resource" }
700
- ```
701
-
702
- #### ManyToMany Example
703
- ```json
704
- { "from": "User", "to": "Role", "type": "ManyToMany", "to_name": "roles" }
705
- ```
706
-
707
- ### 5. Testing and Validation Guide
708
-
709
- - **Validate Relationship Generation:**
710
- - After running `apso server scaffold`, inspect the generated entity files in the `autogen` directory (never modify these directly—see above for extension instructions).
711
- - Check that only one property exists for each relationship per entity.
712
- - Confirm that foreign key columns and decorators are present as expected.
713
-
714
- - **Build Testing Procedures:**
715
- - Run `tsc` or your build process to catch duplicate or missing property errors early.
716
- - Write unit tests for entity relationships if possible.
717
-
718
- - **Entity Inspection Checklist:**
719
- - No duplicate properties or foreign keys
720
- - All relationships have the correct decorators
721
- - No circular imports or eager loading loops
722
-
723
- - **Clean Regeneration Practices:**
724
- - Before re-scaffolding, remove old generated code:
725
- ```sh
726
- rm -rf autogen
727
- apso server scaffold
728
- ```
729
- This prevents stale or duplicate files from causing errors. **Never add custom code to autogen—use extensions as described above.**
730
-
731
- ### 6. Version 2 Format Clarity
732
-
733
- - In v2, relationships and entities are defined in separate arrays:
734
- ```json
735
- {
736
- "version": 2,
737
- "entities": [ ... ],
738
- "relationships": [ ... ]
739
- }
740
- ```
741
- - Each relationship should only be defined once, with clear `to_name` if you want a custom property name.
742
- - Field types and validation are specified per the [Auto-Generated Code Reference](#auto-generated-code-reference).
743
-
744
- > **Summary:**
745
- >
746
- > - Only define one side of each relationship in `.apsorc`.
747
- > - Let Apso CLI auto-generate the inverse side.
748
- > - Avoid deep nesting and duplicate definitions.
749
- > - Always inspect generated code and test your build after scaffolding.
750
-
751
- ## Authentication (Bring Your Own Auth)
752
-
753
- Apso provides flexible, provider-agnostic authentication that generates NestJS guards from your `.apsorc` configuration. Unlike monolithic platforms that force vendor lock-in through proprietary auth systems, Apso embraces **code ownership** - you choose your auth provider, and you own the generated code.
754
-
755
- ### Philosophy: Why Bring Your Own Auth Matters
756
-
757
- Traditional BaaS platforms like Supabase and Firebase provide authentication as a core feature, but this creates dependency:
758
- - Your user data lives in their systems
759
- - Migrating away requires rewriting auth logic
760
- - You're bound to their pricing, features, and roadmap
761
-
762
- **Apso takes a different approach:**
763
- - **Provider flexibility** - Use Better Auth, Auth0, Cognito, Clerk, or custom solutions
764
- - **Code ownership** - Generated guards are standard NestJS code you can inspect, modify, and extend
765
- - **Zero lock-in** - Switch providers by changing configuration, not rewriting code
766
- - **Normalized interface** - All providers produce the same `AuthContext` consumed by scoping and RBAC
767
-
768
- This philosophy ensures your authentication layer is **timeless** - it grows with your needs and migrates with your stack.
769
-
770
- ### Supported Authentication Providers
771
-
772
- | Provider | Type | Best For |
773
- |----------|------|----------|
774
- | `better-auth` | Database Sessions | Self-hosted apps, maximum control |
775
- | `custom-db-session` | Database Sessions | Existing session tables, custom flows |
776
- | `auth0` | JWT | Enterprise SSO, social login |
777
- | `clerk` | JWT | Modern SaaS with prebuilt UI |
778
- | `cognito` | JWT | AWS ecosystem integration |
779
- | `api-key` | API Keys | Service-to-service auth, public APIs |
780
-
781
- ### Basic Configuration
782
-
783
- Add an `auth` block to your `.apsorc`:
784
-
785
- ```json
786
- {
787
- "version": 2,
788
- "auth": {
789
- "provider": "better-auth"
790
- },
791
- "entities": [...]
792
- }
100
+ ```bash
101
+ apso generate
102
+ apso generate --language python
103
+ apso generate --skip-format
793
104
  ```
794
105
 
795
- Run `apso server scaffold` to generate the auth guard.
796
-
797
- ### Provider-Specific Configuration
106
+ | Option | Description | Default |
107
+ |--------|-------------|---------|
108
+ | `-l, --language` | Target language (`typescript`, `python`, `go`) | _(from .apsorc or prompted)_ |
109
+ | `--skip-format` | Skip Prettier formatting after generation | `false` |
798
110
 
799
- #### Better Auth / Custom DB Sessions
111
+ Generated files are placed in `src/autogen/`. These files are overwritten on each run. Place custom code in `src/extensions/` to avoid losing changes.
800
112
 
801
- For database-backed session authentication:
802
-
803
- ```json
804
- {
805
- "auth": {
806
- "provider": "better-auth",
807
- "sessionEntity": "session",
808
- "userEntity": "User",
809
- "accountUserEntity": "AccountUser",
810
- "cookiePrefix": "myapp",
811
- "organizationField": "organizationId",
812
- "roleField": "role"
813
- }
814
- }
815
- ```
113
+ ### `apso dev`
816
114
 
817
- | Option | Default | Description |
818
- |--------|---------|-------------|
819
- | `sessionEntity` | `"session"` | Entity storing session tokens |
820
- | `userEntity` | `"User"` | Entity for user accounts |
821
- | `accountUserEntity` | `"AccountUser"` | Junction entity for user-org mapping |
822
- | `cookiePrefix` | service name | Cookie name prefix (e.g., `myapp.session_token`) |
823
- | `organizationField` | `"organizationId"` | Field on accountUserEntity for org ID |
824
- | `roleField` | `"role"` | Field on accountUserEntity for user role |
825
-
826
- #### JWT Providers (Auth0, Clerk, Cognito)
827
-
828
- For JWT-based authentication:
829
-
830
- ```json
831
- {
832
- "auth": {
833
- "provider": "auth0",
834
- "jwt": {
835
- "issuer": "https://your-tenant.auth0.com/",
836
- "audience": "https://your-api.example.com",
837
- "jwksUri": "https://your-tenant.auth0.com/.well-known/jwks.json",
838
- "algorithms": ["RS256"]
839
- },
840
- "claims": {
841
- "userId": "sub",
842
- "email": "email",
843
- "organizationId": "org_id",
844
- "roles": "permissions"
845
- }
846
- }
847
- }
848
- ```
115
+ Start the local development server using Docker Compose.
849
116
 
850
- | JWT Option | Default | Description |
851
- |------------|---------|-------------|
852
- | `issuer` | Required | JWT issuer URL |
853
- | `audience` | Required | Expected JWT audience |
854
- | `jwksUri` | `issuer + /.well-known/jwks.json` | JWKS endpoint for key rotation |
855
- | `algorithms` | `["RS256"]` | Accepted signing algorithms |
856
-
857
- | Claims Option | Default | Description |
858
- |---------------|---------|-------------|
859
- | `userId` | `"sub"` | Claim containing user ID |
860
- | `email` | `"email"` | Claim containing user email |
861
- | `organizationId` | - | Claim for org/workspace ID |
862
- | `roles` | `"roles"` | Claim containing role array |
863
-
864
- #### API Key Authentication
865
-
866
- For service-to-service or public API authentication:
867
-
868
- ```json
869
- {
870
- "auth": {
871
- "provider": "api-key",
872
- "apiKeyHeader": "x-api-key",
873
- "apiKeyEntity": "ApiKey",
874
- "workspaceField": "workspaceId",
875
- "roleField": "permissions"
876
- }
877
- }
117
+ ```bash
118
+ apso dev
119
+ apso dev --build
120
+ apso dev --detach
878
121
  ```
879
122
 
880
- | Option | Default | Description |
881
- |--------|---------|-------------|
882
- | `apiKeyHeader` | `"x-api-key"` | Header name for API key |
883
- | `apiKeyEntity` | `"ApiKey"` | Entity storing API keys |
884
- | `workspaceField` | - | Field on entity for workspace ID |
885
- | `roleField` | - | Field on entity for permissions |
886
-
887
- ### The AuthContext Interface
888
-
889
- All auth providers produce a normalized `AuthContext` that's attached to every authenticated request:
890
-
891
- ```typescript
892
- interface AuthContext {
893
- userId?: string; // The authenticated user's ID
894
- email?: string; // User's email (if available)
895
- workspaceId?: string; // Workspace/tenant ID
896
- organizationId?: string; // Organization ID (alias)
897
- roles: string[]; // User's roles/permissions
898
- serviceId?: string; // For API key auth: the key identifier
899
- user?: unknown; // Raw user object (provider-specific)
900
- session?: unknown; // Raw session object (provider-specific)
901
- }
902
- ```
123
+ | Option | Description | Default |
124
+ |--------|-------------|---------|
125
+ | `--build` | Rebuild images before starting | `false` |
126
+ | `-d, --detach` | Run containers in the background | `false` |
903
127
 
904
- This normalization is powerful - your business logic works with the same interface regardless of whether you're using Auth0 JWTs or Better Auth sessions.
128
+ Requires Docker and Docker Compose. Looks for `docker-compose.yml` in the current directory.
905
129
 
906
- ### Generated Files
130
+ ### `apso migrate`
907
131
 
908
- When `auth` is configured, `apso server scaffold` generates:
132
+ Detect schema changes, generate migration SQL, and test against a local PGlite sandbox. No Docker or external database required.
909
133
 
134
+ ```bash
135
+ apso migrate # Detect changes, generate and test SQL
136
+ apso migrate --apply # Update snapshot after successful test
137
+ apso migrate --reset # Clear sandbox and start fresh
138
+ apso migrate --sql # Output raw SQL only (for piping)
910
139
  ```
911
- src/
912
- guards/
913
- auth.guard.ts # Authentication guard implementation
914
- scope.guard.ts # Data scoping guard (if scopeBy used)
915
- guards.module.ts # NestJS module with providers
916
- index.ts # Exports
917
- ```
918
-
919
- ### Enabling Authentication
920
-
921
- Guards are generated but **not enabled globally by default**. Enable them based on your needs:
922
140
 
923
- #### Option 1: Global Enable (Recommended for most apps)
141
+ | Option | Description | Default |
142
+ |--------|-------------|---------|
143
+ | `--apply` | Update local schema snapshot after verified migration | `false` |
144
+ | `--reset` | Reset the sandbox (clear snapshot and PGlite data) | `false` |
145
+ | `--sql` | Output raw SQL statements only | `false` |
924
146
 
925
- Edit `src/guards/guards.module.ts`:
926
-
927
- ```typescript
928
- providers: [
929
- AuthGuard,
930
- ScopeGuard,
931
- // Uncomment to enable globally:
932
- {
933
- provide: APP_GUARD,
934
- useClass: AuthGuard,
935
- },
936
- {
937
- provide: APP_GUARD,
938
- useClass: ScopeGuard,
939
- },
940
- ],
941
- ```
147
+ The sandbox works by comparing your current `.apsorc` against the last-known snapshot, generating the migration SQL, and executing it against an in-process Postgres instance (PGlite). If the migration fails locally, you know before it reaches any real database.
942
148
 
943
- #### Option 2: Controller-Level Enable
149
+ ### `apso deploy`
944
150
 
945
- ```typescript
946
- import { AuthGuard } from '../guards';
151
+ Deploy the linked service to the Apso platform. Runs a local migration check before deploying.
947
152
 
948
- @UseGuards(AuthGuard)
949
- @Controller('projects')
950
- export class ProjectController { }
153
+ ```bash
154
+ apso deploy
155
+ apso deploy --yes
156
+ apso deploy --skip-migrate
157
+ apso deploy --no-wait
951
158
  ```
952
159
 
953
- #### Option 3: Route-Level Enable
954
-
955
- ```typescript
956
- @UseGuards(AuthGuard)
957
- @Get('me')
958
- getProfile(@Req() req: AuthenticatedRequest) {
959
- return req.auth;
960
- }
961
- ```
160
+ | Option | Description | Default |
161
+ |--------|-------------|---------|
162
+ | `-y, --yes` | Skip confirmation prompt | `false` |
163
+ | `--skip-migrate` | Skip local migration validation | `false` |
164
+ | `--no-wait` | Trigger deploy without waiting for completion | `false` |
962
165
 
963
- ### Decorators
166
+ If schema changes are detected, `apso deploy` shows the migration SQL and asks for confirmation before proceeding. If the migration fails locally, the deploy is blocked.
964
167
 
965
- ```typescript
966
- import { Public, SkipScopeCheck } from './guards';
168
+ ### `apso login`
967
169
 
968
- // Skip ALL guards (no authentication required)
969
- @Public()
970
- @Get('health')
971
- healthCheck() { }
170
+ Authenticate with the Apso platform via browser-based OAuth or API token.
972
171
 
973
- // Skip only scope checking (auth still required)
974
- @SkipScopeCheck()
975
- @Get('admin/stats')
976
- adminStats() { }
172
+ ```bash
173
+ apso login
174
+ apso login --token <api-token>
977
175
  ```
978
176
 
979
- ### Accessing Auth Context
177
+ | Option | Description |
178
+ |--------|-------------|
179
+ | `-t, --token` | API token for non-interactive login (CI/CD) |
980
180
 
981
- In controllers and services, access the authenticated context:
181
+ ### `apso logout`
982
182
 
983
- ```typescript
984
- import { AuthenticatedRequest, getAuthContext, requireAuthContext } from './guards';
183
+ Clear stored credentials.
985
184
 
986
- @Controller('projects')
987
- export class ProjectController {
988
- @Get()
989
- findAll(@Req() req: AuthenticatedRequest) {
990
- // Direct access
991
- const userId = req.auth.userId;
992
- const orgId = req.auth.organizationId;
993
-
994
- // Or use helpers
995
- const ctx = requireAuthContext(req); // Throws if not authenticated
996
- return this.projectService.findByOrg(ctx.organizationId);
997
- }
998
- }
999
- ```
1000
-
1001
- ### Token Extraction
1002
-
1003
- The generated auth guard extracts tokens from multiple locations (in order):
1004
-
1005
- 1. **Authorization header**: `Bearer <token>`
1006
- 2. **Cookies**: `{cookiePrefix}.session_token`, `better-auth.session_token`, or `session_token`
1007
- 3. **Custom header**: `X-Session-Token`
1008
-
1009
- This flexibility supports both browser-based apps (cookies) and API clients (headers).
1010
-
1011
- ### Complete Example
1012
-
1013
- ```json
1014
- {
1015
- "version": 2,
1016
- "auth": {
1017
- "provider": "better-auth",
1018
- "sessionEntity": "session",
1019
- "userEntity": "User",
1020
- "accountUserEntity": "AccountUser",
1021
- "cookiePrefix": "myapp",
1022
- "organizationField": "organizationId",
1023
- "roleField": "role"
1024
- },
1025
- "entities": [
1026
- {
1027
- "name": "User",
1028
- "fields": [
1029
- { "name": "email", "type": "text", "unique": true },
1030
- { "name": "name", "type": "text", "nullable": true }
1031
- ]
1032
- },
1033
- {
1034
- "name": "session",
1035
- "fields": [
1036
- { "name": "token", "type": "text", "unique": true },
1037
- { "name": "expiresAt", "type": "timestamp" },
1038
- { "name": "userId", "type": "text" }
1039
- ]
1040
- },
1041
- {
1042
- "name": "Organization",
1043
- "fields": [
1044
- { "name": "name", "type": "text" }
1045
- ]
1046
- },
1047
- {
1048
- "name": "AccountUser",
1049
- "fields": [
1050
- { "name": "role", "type": "enum", "values": ["owner", "admin", "member"] }
1051
- ]
1052
- },
1053
- {
1054
- "name": "Project",
1055
- "scopeBy": "organizationId",
1056
- "fields": [
1057
- { "name": "name", "type": "text" }
1058
- ]
1059
- }
1060
- ],
1061
- "relationships": [
1062
- { "from": "AccountUser", "to": "User", "type": "ManyToOne" },
1063
- { "from": "AccountUser", "to": "Organization", "type": "ManyToOne" },
1064
- { "from": "Project", "to": "Organization", "type": "ManyToOne" }
1065
- ]
1066
- }
185
+ ```bash
186
+ apso logout
1067
187
  ```
1068
188
 
1069
- ### Migrating Between Providers
1070
-
1071
- One of Apso's key advantages is seamless provider migration:
1072
-
1073
- 1. Update the `auth` block in `.apsorc`
1074
- 2. Run `apso server scaffold`
1075
- 3. Update your frontend to use the new provider's login flow
1076
-
1077
- Your business logic remains unchanged because it only interacts with the normalized `AuthContext`.
1078
-
1079
- ---
189
+ ### `apso whoami`
1080
190
 
1081
- ## Data Scoping (Multi-Tenant Isolation)
191
+ Display information about the current authenticated user and linked project.
1082
192
 
1083
- Apso provides application-layer data isolation that rivals PostgreSQL's Row-Level Security (RLS) - but with greater flexibility, visibility, and portability. This is Apso's answer to one of the most critical challenges in SaaS development: ensuring users only see and modify data they're authorized to access.
1084
-
1085
- ### Philosophy: Application-Layer RLS
1086
-
1087
- Traditional approaches to multi-tenant data isolation include:
1088
- - **Database RLS (Supabase)** - Powerful but opaque, tied to PostgreSQL, difficult to debug
1089
- - **Manual filtering** - Error-prone, repetitive, easy to forget on new endpoints
1090
- - **ORM middleware** - Often complex, hard to customize
1091
-
1092
- **Apso's approach delivers the best of all worlds:**
1093
- - **Declarative** - Define scope once in `.apsorc`, applied everywhere
1094
- - **Transparent** - Generated guards are standard NestJS code you can inspect and debug
1095
- - **Portable** - Works with any database, not locked to PostgreSQL RLS
1096
- - **Flexible** - Configure per-entity behavior: auto-injection, filtering, bypass rules
1097
-
1098
- This design is **timeless** - your data isolation logic is explicit code, not hidden database magic.
1099
-
1100
- ### What is scopeBy?
1101
-
1102
- The `scopeBy` property on entities defines which field(s) determine the authorization scope for that entity. When configured, Apso generates NestJS guards that:
1103
-
1104
- - **Auto-inject** scope values on create operations (POST requests)
1105
- - **Auto-filter** queries by scope values on list operations (GET without ID)
1106
- - **Verify ownership** on single-resource operations (GET/PUT/PATCH/DELETE by ID)
1107
-
1108
- This is Apso's answer to PostgreSQL Row-Level Security (RLS), implemented at the application layer for flexibility and visibility.
1109
-
1110
- ### Basic Example
1111
-
1112
- ```json
1113
- {
1114
- "version": 2,
1115
- "entities": [
1116
- {
1117
- "name": "Project",
1118
- "scopeBy": "workspaceId",
1119
- "fields": [
1120
- { "name": "name", "type": "text" }
1121
- ]
1122
- }
1123
- ]
1124
- }
193
+ ```bash
194
+ apso whoami
1125
195
  ```
1126
196
 
1127
- This generates a guard that ensures:
1128
- - All Project queries filter by `workspaceId` from the request context
1129
- - New Projects automatically get the `workspaceId` injected
1130
- - Single Project access verifies the Project belongs to the user's workspace
1131
-
1132
- ### scopeBy Configuration Options
1133
-
1134
- #### Single Field Scoping
1135
- ```json
1136
- {
1137
- "name": "Project",
1138
- "scopeBy": "workspaceId"
1139
- }
1140
- ```
197
+ ### `apso link`
1141
198
 
1142
- #### Multiple Field Scoping
1143
- ```json
1144
- {
1145
- "name": "Task",
1146
- "scopeBy": ["workspaceId", "projectId"]
1147
- }
1148
- ```
199
+ Link the current project to a platform service.
1149
200
 
1150
- #### Nested Path Scoping
1151
- For entities that don't have a direct scope field but inherit scope through a relationship:
1152
- ```json
1153
- {
1154
- "name": "Comment",
1155
- "scopeBy": "task.workspaceId"
1156
- }
1157
- ```
1158
- This tells the guard to look up the Task relationship and verify the workspaceId through that path.
1159
-
1160
- ### scopeOptions
1161
-
1162
- Fine-tune scoping behavior with `scopeOptions`:
1163
-
1164
- ```json
1165
- {
1166
- "name": "AuditLog",
1167
- "scopeBy": "workspaceId",
1168
- "scopeOptions": {
1169
- "injectOnCreate": false,
1170
- "enforceOn": ["find", "get"],
1171
- "bypassRoles": ["admin", "superadmin"]
1172
- }
1173
- }
201
+ ```bash
202
+ apso link
203
+ apso link --workspace my-team --service my-api
204
+ apso link --force
1174
205
  ```
1175
206
 
1176
- | Option | Type | Default | Description |
1177
- |--------|------|---------|-------------|
1178
- | `injectOnCreate` | boolean | `true` | Auto-inject scope value on POST requests |
1179
- | `enforceOn` | string[] | `["find", "get", "create", "update", "delete"]` | Operations where scope is enforced |
1180
- | `bypassRoles` | string[] | `[]` | Roles that skip scope checking |
207
+ | Option | Description |
208
+ |--------|-------------|
209
+ | `-w, --workspace` | Workspace slug |
210
+ | `-s, --service` | Service slug |
211
+ | `-f, --force` | Overwrite existing link without confirmation |
1181
212
 
1182
- ### Generated Files
213
+ ### `apso unlink`
1183
214
 
1184
- When entities have `scopeBy` configured, `apso server scaffold` generates:
215
+ Remove the link between the current project and the platform.
1185
216
 
217
+ ```bash
218
+ apso unlink
1186
219
  ```
1187
- src/
1188
- guards/
1189
- scope.guard.ts # Main guard implementation
1190
- guards.module.ts # NestJS module with providers
1191
- index.ts # Exports
1192
- ```
1193
-
1194
- ### Enabling Guards
1195
220
 
1196
- Guards are generated but **not enabled globally by default** (for backward compatibility). To enable:
221
+ ### `apso status`
1197
222
 
1198
- #### Option 1: Global Enable (Recommended)
1199
- Uncomment the APP_GUARD provider in `src/guards/guards.module.ts`:
223
+ Show the current service and latest build status.
1200
224
 
1201
- ```typescript
1202
- providers: [
1203
- ScopeGuard,
1204
- // Uncomment to enable globally:
1205
- {
1206
- provide: APP_GUARD,
1207
- useClass: ScopeGuard,
1208
- },
1209
- ],
225
+ ```bash
226
+ apso status
1210
227
  ```
1211
228
 
1212
- #### Option 2: Per-Controller Enable
1213
- Apply to specific controllers:
229
+ ### `apso logs`
1214
230
 
1215
- ```typescript
1216
- import { ScopeGuard } from '../guards';
231
+ View build logs for the linked service.
1217
232
 
1218
- @UseGuards(ScopeGuard)
1219
- @Controller('projects')
1220
- export class ProjectController { }
1221
- ```
1222
-
1223
- #### Option 3: Per-Route Enable
1224
- Apply to specific routes:
1225
-
1226
- ```typescript
1227
- @UseGuards(ScopeGuard)
1228
- @Get(':id')
1229
- findOne(@Param('id') id: string) { }
233
+ ```bash
234
+ apso logs
235
+ apso logs <build-id>
1230
236
  ```
1231
237
 
1232
- ### Decorators
1233
-
1234
- The generated guard supports these decorators:
238
+ ### `apso open`
1235
239
 
1236
- ```typescript
1237
- import { Public, SkipScopeCheck } from './guards';
240
+ Open the service dashboard or API endpoint in the browser.
1238
241
 
1239
- @Public() // Skip ALL guards for this route
1240
- @Get('public-endpoint')
1241
- publicRoute() { }
1242
-
1243
- @SkipScopeCheck() // Skip only scope checking (other guards still run)
1244
- @Get('admin-dashboard')
1245
- adminRoute() { }
242
+ ```bash
243
+ apso open
1246
244
  ```
1247
245
 
1248
- ### Request Context Integration
246
+ ### `apso projects`
1249
247
 
1250
- The guard expects scope values in the request object. Set these in your authentication middleware:
248
+ List services in a workspace.
1251
249
 
1252
- ```typescript
1253
- // In your auth middleware
1254
- request.workspaceId = user.currentWorkspaceId;
1255
- request.user = { roles: ['user'] };
1256
- ```
1257
-
1258
- ### Complete Example
1259
-
1260
- ```json
1261
- {
1262
- "version": 2,
1263
- "entities": [
1264
- {
1265
- "name": "Workspace",
1266
- "fields": [{ "name": "name", "type": "text" }]
1267
- },
1268
- {
1269
- "name": "Project",
1270
- "scopeBy": "workspaceId",
1271
- "fields": [{ "name": "name", "type": "text" }]
1272
- },
1273
- {
1274
- "name": "Task",
1275
- "scopeBy": ["workspaceId", "projectId"],
1276
- "fields": [{ "name": "title", "type": "text" }]
1277
- },
1278
- {
1279
- "name": "Comment",
1280
- "scopeBy": "task.workspaceId",
1281
- "scopeOptions": {
1282
- "enforceOn": ["find", "get", "create", "delete"]
1283
- },
1284
- "fields": [{ "name": "text", "type": "text" }]
1285
- }
1286
- ],
1287
- "relationships": [
1288
- { "from": "Project", "to": "Workspace", "type": "ManyToOne" },
1289
- { "from": "Task", "to": "Project", "type": "ManyToOne" },
1290
- { "from": "Comment", "to": "Task", "type": "ManyToOne" }
1291
- ]
1292
- }
250
+ ```bash
251
+ apso projects
1293
252
  ```
1294
253
 
1295
- ### Scoping vs Authorization
1296
-
1297
- **Scoping** (what `scopeBy` provides):
1298
- - Answers: "Which rows can this user see/modify?"
1299
- - Data isolation based on tenant/workspace membership
1300
- - Automatic filtering and injection
1301
-
1302
- **Authorization** (separate concern, not covered by `scopeBy`):
1303
- - Answers: "Can this user perform this action?"
1304
- - Role-based access control (RBAC)
1305
- - Permission checking (create, read, update, delete)
1306
-
1307
- These are intentionally separate. Use `scopeBy` for data isolation, and implement authorization guards separately for permission checking.
1308
-
1309
- ---
254
+ ### `apso config`
1310
255
 
1311
- ## Authentication + Scoping: Working Together
256
+ View or modify CLI configuration.
1312
257
 
1313
- Auth and Scoping are designed to work together seamlessly. This combined system delivers enterprise-grade security with minimal configuration.
1314
-
1315
- ### How They Connect
1316
-
1317
- When both `auth` and `scopeBy` are configured:
1318
-
1319
- 1. **AuthGuard runs first** - Validates the session/token and populates `request.auth`
1320
- 2. **ScopeGuard runs second** - Reads `organizationId`/`workspaceId` from `request.auth` and enforces isolation
1321
-
1322
- The `AuthContext` automatically provides the scope values that `scopeBy` needs:
1323
-
1324
- ```typescript
1325
- // AuthGuard sets this on every authenticated request:
1326
- request.auth = {
1327
- userId: "user_123",
1328
- organizationId: "org_456", // <-- ScopeGuard uses this
1329
- workspaceId: "org_456", // <-- Or this (alias)
1330
- roles: ["admin"],
1331
- // ...
1332
- }
1333
-
1334
- // ScopeGuard then uses organizationId to:
1335
- // - Filter GET /projects -> only org_456's projects
1336
- // - Inject on POST /projects -> auto-set organizationId
1337
- // - Verify on GET /projects/:id -> ensure it belongs to org_456
1338
- ```
1339
-
1340
- ### Complete Multi-Tenant Example
1341
-
1342
- ```json
1343
- {
1344
- "version": 2,
1345
- "auth": {
1346
- "provider": "better-auth",
1347
- "sessionEntity": "session",
1348
- "userEntity": "User",
1349
- "accountUserEntity": "AccountUser",
1350
- "organizationField": "organizationId"
1351
- },
1352
- "entities": [
1353
- {
1354
- "name": "User",
1355
- "fields": [
1356
- { "name": "email", "type": "text", "unique": true },
1357
- { "name": "name", "type": "text", "nullable": true }
1358
- ]
1359
- },
1360
- {
1361
- "name": "session",
1362
- "fields": [
1363
- { "name": "token", "type": "text", "unique": true },
1364
- { "name": "expiresAt", "type": "timestamp" },
1365
- { "name": "userId", "type": "text" }
1366
- ]
1367
- },
1368
- {
1369
- "name": "Organization",
1370
- "fields": [
1371
- { "name": "name", "type": "text" },
1372
- { "name": "plan", "type": "enum", "values": ["free", "pro", "enterprise"] }
1373
- ]
1374
- },
1375
- {
1376
- "name": "AccountUser",
1377
- "fields": [
1378
- { "name": "role", "type": "enum", "values": ["owner", "admin", "member"] }
1379
- ]
1380
- },
1381
- {
1382
- "name": "Project",
1383
- "scopeBy": "organizationId",
1384
- "fields": [
1385
- { "name": "name", "type": "text" },
1386
- { "name": "status", "type": "enum", "values": ["active", "archived"] }
1387
- ]
1388
- },
1389
- {
1390
- "name": "Task",
1391
- "scopeBy": ["organizationId", "projectId"],
1392
- "fields": [
1393
- { "name": "title", "type": "text" },
1394
- { "name": "completed", "type": "boolean", "default": false }
1395
- ]
1396
- },
1397
- {
1398
- "name": "AuditLog",
1399
- "scopeBy": "organizationId",
1400
- "scopeOptions": {
1401
- "injectOnCreate": true,
1402
- "enforceOn": ["find", "get"],
1403
- "bypassRoles": ["superadmin"]
1404
- },
1405
- "fields": [
1406
- { "name": "action", "type": "text" },
1407
- { "name": "details", "type": "json" }
1408
- ]
1409
- }
1410
- ],
1411
- "relationships": [
1412
- { "from": "AccountUser", "to": "User", "type": "ManyToOne" },
1413
- { "from": "AccountUser", "to": "Organization", "type": "ManyToOne" },
1414
- { "from": "Project", "to": "Organization", "type": "ManyToOne" },
1415
- { "from": "Task", "to": "Project", "type": "ManyToOne" },
1416
- { "from": "Task", "to": "Organization", "type": "ManyToOne" },
1417
- { "from": "AuditLog", "to": "Organization", "type": "ManyToOne" }
1418
- ]
1419
- }
1420
- ```
1421
-
1422
- ### Guard Execution Order
1423
-
1424
- Enable both guards globally for automatic protection:
1425
-
1426
- ```typescript
1427
- // src/guards/guards.module.ts
1428
- providers: [
1429
- AuthGuard,
1430
- ScopeGuard,
1431
- {
1432
- provide: APP_GUARD,
1433
- useClass: AuthGuard, // Runs first
1434
- },
1435
- {
1436
- provide: APP_GUARD,
1437
- useClass: ScopeGuard, // Runs second
1438
- },
1439
- ],
258
+ ```bash
259
+ apso config # Show all settings
260
+ apso config get apiUrl # Get a specific value
261
+ apso config set verbose true # Set a value
262
+ apso config reset # Reset to defaults
1440
263
  ```
1441
264
 
1442
- ### The Apso Security Stack
1443
-
1444
- | Layer | Guard | Question Answered | Configuration |
1445
- |-------|-------|-------------------|---------------|
1446
- | 1. Identity | AuthGuard | "Who is this user?" | `auth` in `.apsorc` |
1447
- | 2. Isolation | ScopeGuard | "Which data can they see?" | `scopeBy` on entities |
1448
- | 3. Authorization | (Your implementation) | "What actions can they take?" | Custom RBAC guard |
1449
-
1450
- Apso handles layers 1 and 2 automatically. Layer 3 (fine-grained permissions like "can edit this specific resource") is left to your business logic since it varies widely between applications.
265
+ **Configuration keys:**
1451
266
 
1452
- ### Why This Matters: The Supabase Comparison
267
+ | Key | Type | Description |
268
+ |-----|------|-------------|
269
+ | `apiUrl` | string | Platform API URL |
270
+ | `webUrl` | string | Platform web URL |
271
+ | `verbose` | boolean | Enable verbose output |
272
+ | `noColor` | boolean | Disable colored output |
273
+ | `telemetryDisabled` | boolean | Opt out of anonymous telemetry |
274
+ | `defaultWorkspace` | string | Default workspace slug |
1453
275
 
1454
- | Feature | Supabase | Apso |
1455
- |---------|----------|------|
1456
- | **Auth** | Built-in, proprietary | Bring your own, code you own |
1457
- | **Data Isolation** | PostgreSQL RLS (opaque) | Application-layer guards (transparent) |
1458
- | **Portability** | Locked to Supabase | Works with any database |
1459
- | **Debugging** | Database logs, hard to trace | Standard NestJS code, full visibility |
1460
- | **Customization** | Limited to RLS policies | Unlimited - it's your code |
1461
- | **Migration Path** | Rewrite required | Change config, regenerate |
276
+ Boolean values accept `true`/`false` or `1`/`0`.
1462
277
 
1463
- Apso delivers equivalent functionality to Supabase's auth + RLS combo, but with:
1464
- - **Full code ownership** - No vendor lock-in
1465
- - **Provider flexibility** - Auth0, Clerk, Cognito, or self-hosted
1466
- - **Database freedom** - PostgreSQL, MySQL, MongoDB, or any TypeORM-supported database
1467
- - **Complete transparency** - Debug with standard tools, not vendor-specific dashboards
278
+ Environment variables override config file values:
1468
279
 
1469
- This architecture is **timeless** - it grows with your needs, migrates with your stack, and remains fully under your control.
280
+ | Variable | Overrides |
281
+ |----------|-----------|
282
+ | `APSO_API_URL` | `apiUrl` |
283
+ | `APSO_WEB_URL` | `webUrl` |
284
+ | `APSO_DEBUG=true` | `verbose` |
285
+ | `NO_COLOR` or `APSO_NO_COLOR=true` | `noColor` |
1470
286
 
1471
- ---
287
+ ### `apso schema`
1472
288
 
1473
- ## Schema Reference
289
+ Manage schema synchronization between local `.apsorc` and the platform.
1474
290
 
1475
- The `.apsorc` file must conform to the [APSO Configuration Schema](./apsorc.schema.json). This schema defines all valid properties, types, and constraints for your configuration file.
1476
-
1477
- ### Inline Schema (apsorc.schema.json)
1478
-
1479
- ```json
1480
- // ... see full contents in apso-cli/apsorc.schema.json ...
1481
- {
1482
- "$schema": "http://json-schema.org/draft-07/schema#",
1483
- "title": "APSO Configuration Schema",
1484
- "description": "Schema for the .apsorc file used by APSO to define entities and relationships.",
1485
- // ... (truncated for brevity) ...
1486
- }
291
+ ```bash
292
+ apso schema validate # Validate local schema
293
+ apso schema diff # Show diff between local and remote
294
+ apso schema push # Push local schema to platform
295
+ apso schema pull # Pull remote schema to local
1487
296
  ```
1488
297
 
1489
- See the [full schema file](./apsorc.schema.json) for all details and validation rules.
1490
-
1491
- ## Debugging
298
+ ## Global options
1492
299
 
1493
- For debugging we would use the debug package so you need to import the package in file where you want to debug any code.
300
+ These options work with any command:
1494
301
 
1495
- ```sh-session
1496
- var debug = require("debug")("{name of your choice}");
1497
- ```
1498
-
1499
- Then just add the debug statements wherever you want.
1500
-
1501
- ```sh-session
1502
- debug(`variable1 value is:`, variable1);
302
+ ```bash
303
+ apso [command] --help # Show command help
304
+ apso [command] --version # Show CLI version
1503
305
  ```
1504
306
 
1505
- Now inorder to see the debug output you need to run the cli in debug mode like this.
307
+ ## Supported languages
1506
308
 
1507
- ```sh-session
1508
- env DEBUG=\* ./bin/run server scaffold
1509
- ```
309
+ | Language | Framework | ORM | Status |
310
+ |----------|-----------|-----|--------|
311
+ | TypeScript | NestJS | TypeORM | Stable |
312
+ | Python | FastAPI | SQLAlchemy | In development |
313
+ | Go | Gin | GORM | In development |
1510
314
 
1511
- So we are setting the env variable DEBUG and then giving the path of the bin/run file and then next will be your cli commmand.
315
+ ## Contribute
1512
316
 
1513
- Note: Whenever you make a change you need to rebuild the cli before running it in order to reflect the changes.
1514
-
1515
- ```sh-session
317
+ ```bash
318
+ git clone https://github.com/apsoai/cli.git
319
+ cd cli
320
+ npm install
1516
321
  npm run build
1517
322
  ```
1518
323
 
1519
- # Commands
1520
-
1521
- - [`apso help [COMMANDS]`](#apso-help-commands)
1522
- - [`apso plugins`](#apso-plugins)
1523
- - [`apso plugins:install PLUGIN...`](#apso-pluginsinstall-plugin)
1524
- - [`apso plugins:inspect PLUGIN...`](#apso-pluginsinspect-plugin)
1525
- - [`apso plugins:install PLUGIN...`](#apso-pluginsinstall-plugin-1)
1526
- - [`apso plugins:link PLUGIN`](#apso-pluginslink-plugin)
1527
- - [`apso plugins:uninstall PLUGIN...`](#apso-pluginsuninstall-plugin)
1528
- - [`apso plugins:uninstall PLUGIN...`](#apso-pluginsuninstall-plugin-1)
1529
- - [`apso plugins:uninstall PLUGIN...`](#apso-pluginsuninstall-plugin-2)
1530
- - [`apso plugins update`](#apso-plugins-update)
1531
-
1532
- ## `apso help [COMMANDS]`
1533
-
1534
- Display help for apso.
1535
-
1536
- ```
1537
- USAGE
1538
- $ apso help [COMMANDS] [-n]
1539
-
1540
- ARGUMENTS
1541
- COMMANDS Command to show help for.
1542
-
1543
- FLAGS
1544
- -n, --nested-commands Include all nested commands in the output.
1545
-
1546
- DESCRIPTION
1547
- Display help for apso.
1548
- ```
1549
-
1550
- _See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v5.2.9/src/commands/help.ts)_
1551
-
1552
- ## `apso plugins`
1553
-
1554
- List installed plugins.
1555
-
1556
- ```
1557
- USAGE
1558
- $ apso plugins [--json] [--core]
1559
-
1560
- FLAGS
1561
- --core Show core plugins.
1562
-
1563
- GLOBAL FLAGS
1564
- --json Format output as json.
1565
-
1566
- DESCRIPTION
1567
- List installed plugins.
1568
-
1569
- EXAMPLES
1570
- $ apso plugins
1571
- ```
1572
-
1573
- _See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v3.1.2/src/commands/plugins/index.ts)_
1574
-
1575
- ## `apso plugins:install PLUGIN...`
1576
-
1577
- Installs a plugin into the CLI.
1578
-
1579
- ```
1580
- USAGE
1581
- $ apso plugins:install PLUGIN...
1582
-
1583
- ARGUMENTS
1584
- PLUGIN Plugin to install.
1585
-
1586
- FLAGS
1587
- -f, --force Run yarn install with force flag.
1588
- -h, --help Show CLI help.
1589
- -v, --verbose
1590
-
1591
- DESCRIPTION
1592
- Installs a plugin into the CLI.
1593
- Can be installed from npm or a git url.
324
+ To run commands from the local build:
1594
325
 
1595
- Installation of a user-installed plugin will override a core plugin.
1596
-
1597
- e.g. If you have a core plugin that has a 'hello' command, installing a user-installed plugin with a 'hello' command
1598
- will override the core plugin implementation. This is useful if a user needs to update core plugin functionality in
1599
- the CLI without the need to patch and update the whole CLI.
1600
-
1601
-
1602
- ALIASES
1603
- $ apso plugins add
1604
-
1605
- EXAMPLES
1606
- $ apso plugins:install myplugin
1607
-
1608
- $ apso plugins:install https://github.com/someuser/someplugin
1609
-
1610
- $ apso plugins:install someuser/someplugin
1611
- ```
1612
-
1613
- ## `apso plugins:inspect PLUGIN...`
1614
-
1615
- Displays installation properties of a plugin.
1616
-
1617
- ```
1618
- USAGE
1619
- $ apso plugins:inspect PLUGIN...
1620
-
1621
- ARGUMENTS
1622
- PLUGIN [default: .] Plugin to inspect.
1623
-
1624
- FLAGS
1625
- -h, --help Show CLI help.
1626
- -v, --verbose
1627
-
1628
- GLOBAL FLAGS
1629
- --json Format output as json.
1630
-
1631
- DESCRIPTION
1632
- Displays installation properties of a plugin.
1633
-
1634
- EXAMPLES
1635
- $ apso plugins:inspect myplugin
1636
- ```
1637
-
1638
- ## `apso plugins:install PLUGIN...`
1639
-
1640
- Installs a plugin into the CLI.
1641
-
1642
- ```
1643
- USAGE
1644
- $ apso plugins:install PLUGIN...
1645
-
1646
- ARGUMENTS
1647
- PLUGIN Plugin to install.
1648
-
1649
- FLAGS
1650
- -f, --force Run yarn install with force flag.
1651
- -h, --help Show CLI help.
1652
- -v, --verbose
1653
-
1654
- DESCRIPTION
1655
- Installs a plugin into the CLI.
1656
- Can be installed from npm or a git url.
1657
-
1658
- Installation of a user-installed plugin will override a core plugin.
1659
-
1660
- e.g. If you have a core plugin that has a 'hello' command, installing a user-installed plugin with a 'hello' command
1661
- will override the core plugin implementation. This is useful if a user needs to update core plugin functionality in
1662
- the CLI without the need to patch and update the whole CLI.
1663
-
1664
-
1665
- ALIASES
1666
- $ apso plugins add
1667
-
1668
- EXAMPLES
1669
- $ apso plugins:install myplugin
1670
-
1671
- $ apso plugins:install https://github.com/someuser/someplugin
1672
-
1673
- $ apso plugins:install someuser/someplugin
1674
- ```
1675
-
1676
- ## `apso plugins:link PLUGIN`
1677
-
1678
- Links a plugin into the CLI for development.
1679
-
1680
- ```
1681
- USAGE
1682
- $ apso plugins:link PLUGIN
1683
-
1684
- ARGUMENTS
1685
- PATH [default: .] path to plugin
1686
-
1687
- FLAGS
1688
- -h, --help Show CLI help.
1689
- -v, --verbose
1690
-
1691
- DESCRIPTION
1692
- Links a plugin into the CLI for development.
1693
- Installation of a linked plugin will override a user-installed or core plugin.
1694
-
1695
- e.g. If you have a user-installed or core plugin that has a 'hello' command, installing a linked plugin with a 'hello'
1696
- command will override the user-installed or core plugin implementation. This is useful for development work.
1697
-
1698
-
1699
- EXAMPLES
1700
- $ apso plugins:link myplugin
1701
- ```
1702
-
1703
- ## `apso plugins:uninstall PLUGIN...`
1704
-
1705
- Removes a plugin from the CLI.
1706
-
1707
- ```
1708
- USAGE
1709
- $ apso plugins:uninstall PLUGIN...
1710
-
1711
- ARGUMENTS
1712
- PLUGIN plugin to uninstall
1713
-
1714
- FLAGS
1715
- -h, --help Show CLI help.
1716
- -v, --verbose
1717
-
1718
- DESCRIPTION
1719
- Removes a plugin from the CLI.
1720
-
1721
- ALIASES
1722
- $ apso plugins unlink
1723
- $ apso plugins remove
1724
- ```
1725
-
1726
- ## `apso plugins:uninstall PLUGIN...`
1727
-
1728
- Removes a plugin from the CLI.
1729
-
1730
- ```
1731
- USAGE
1732
- $ apso plugins:uninstall PLUGIN...
1733
-
1734
- ARGUMENTS
1735
- PLUGIN plugin to uninstall
1736
-
1737
- FLAGS
1738
- -h, --help Show CLI help.
1739
- -v, --verbose
1740
-
1741
- DESCRIPTION
1742
- Removes a plugin from the CLI.
1743
-
1744
- ALIASES
1745
- $ apso plugins unlink
1746
- $ apso plugins remove
326
+ ```bash
327
+ ./bin/run generate
328
+ ./bin/run migrate --sql
1747
329
  ```
1748
330
 
1749
- ## `apso plugins:uninstall PLUGIN...`
1750
-
1751
- Removes a plugin from the CLI.
331
+ To develop continuously:
1752
332
 
333
+ ```bash
334
+ npm run build # Rebuild after changes
335
+ npm link # Make 'apso' command available globally
1753
336
  ```
1754
- USAGE
1755
- $ apso plugins:uninstall PLUGIN...
1756
-
1757
- ARGUMENTS
1758
- PLUGIN plugin to uninstall
1759
-
1760
- FLAGS
1761
- -h, --help Show CLI help.
1762
- -v, --verbose
1763
337
 
1764
- DESCRIPTION
1765
- Removes a plugin from the CLI.
338
+ ### Testing
1766
339
 
1767
- ALIASES
1768
- $ apso plugins unlink
1769
- $ apso plugins remove
340
+ ```bash
341
+ npm run test # Run all tests
342
+ npm run test:watch # Watch mode
343
+ npm run test:cov # Coverage report
1770
344
  ```
1771
345
 
1772
- ## `apso plugins update`
1773
-
1774
- Update installed plugins.
346
+ ### Debugging
1775
347
 
348
+ ```bash
349
+ env DEBUG=* ./bin/run generate
1776
350
  ```
1777
- USAGE
1778
- $ apso plugins update [-h] [-v]
1779
351
 
1780
- FLAGS
1781
- -h, --help Show CLI help.
1782
- -v, --verbose
352
+ ## License
1783
353
 
1784
- DESCRIPTION
1785
- Update installed plugins.
1786
- ```
354
+ MIT