@apso/cli 0.13.0 → 0.15.0
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.
- package/dist/commands/dev.d.ts +54 -2
- package/dist/commands/dev.js +219 -32
- package/dist/commands/doctor.d.ts +10 -0
- package/dist/commands/doctor.js +127 -0
- package/dist/commands/init.js +2 -2
- package/dist/commands/link.js +1 -1
- package/dist/commands/mcp/serve.js +131 -2
- package/dist/commands/projects.js +1 -1
- package/dist/lib/config/types.js +2 -2
- package/dist/lib/doctor/checks.d.ts +35 -0
- package/dist/lib/doctor/checks.js +222 -0
- package/dist/lib/doctor/index.d.ts +4 -0
- package/dist/lib/doctor/index.js +14 -0
- package/dist/lib/doctor/runner.d.ts +29 -0
- package/dist/lib/doctor/runner.js +175 -0
- package/dist/lib/doctor/types.d.ts +35 -0
- package/dist/lib/doctor/types.js +2 -0
- package/dist/lib/migrate/sandbox.js +126 -4
- package/dist/lib/migrate/snapshot.d.ts +1 -0
- package/dist/lib/migrate/snapshot.js +3 -0
- package/dist/lib/templates/go/utils/query.eta +37 -9
- package/dist/lib/templates/python/utils/query.eta +38 -10
- package/dist/lib/utils/template.js +1 -1
- package/npm-shrinkwrap.json +2 -2
- package/oclif.manifest.json +41 -7
- package/package.json +1 -1
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Entity } from "../types/entity";
|
|
2
|
+
import { RelationshipMap } from "../types/relationship";
|
|
3
|
+
import { TargetLanguage } from "../types/generator";
|
|
4
|
+
/**
|
|
5
|
+
* A single finding from a diagnostic check.
|
|
6
|
+
*/
|
|
7
|
+
export interface DiagnosticFinding {
|
|
8
|
+
/** Check identifier (e.g. "pk-strategy") */
|
|
9
|
+
check: string;
|
|
10
|
+
/** Severity level */
|
|
11
|
+
severity: "error" | "warning" | "info";
|
|
12
|
+
/** Human-readable description */
|
|
13
|
+
message: string;
|
|
14
|
+
/** Affected entity name */
|
|
15
|
+
entity?: string;
|
|
16
|
+
/** Affected file path (relative to project root) */
|
|
17
|
+
file?: string;
|
|
18
|
+
/** Suggested fix */
|
|
19
|
+
suggestion?: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Context passed to each diagnostic check function.
|
|
23
|
+
*/
|
|
24
|
+
export interface DiagnosticContext {
|
|
25
|
+
/** Parsed entities from .apsorc */
|
|
26
|
+
entities: Entity[];
|
|
27
|
+
/** Relationship map from .apsorc */
|
|
28
|
+
relationshipMap: RelationshipMap;
|
|
29
|
+
/** Root folder from .apsorc (e.g. "src") */
|
|
30
|
+
rootFolder: string;
|
|
31
|
+
/** Target language */
|
|
32
|
+
language: TargetLanguage;
|
|
33
|
+
/** Absolute path to the project root (where .apsorc lives) */
|
|
34
|
+
projectRoot: string;
|
|
35
|
+
}
|
|
@@ -18,6 +18,7 @@ const paths_1 = require("../config/paths");
|
|
|
18
18
|
const snapshot_1 = require("./snapshot");
|
|
19
19
|
const field_1 = require("../utils/field");
|
|
20
20
|
const casing_1 = require("../utils/casing");
|
|
21
|
+
const relationships_1 = require("../utils/relationships");
|
|
21
22
|
/**
|
|
22
23
|
* Get the sandbox data directory for PGlite persistence.
|
|
23
24
|
*/
|
|
@@ -57,11 +58,12 @@ async function resetPGliteSingleton() {
|
|
|
57
58
|
* Clears TypeORM's global metadata storage first to prevent cross-contamination
|
|
58
59
|
* between runs.
|
|
59
60
|
*/
|
|
60
|
-
function buildEntityClasses(entities) {
|
|
61
|
+
function buildEntityClasses(entities, relationshipMap = {}) {
|
|
61
62
|
var _a, _b;
|
|
62
63
|
clearTypeOrmMetadata();
|
|
63
64
|
const typeorm = require("typeorm");
|
|
64
65
|
const classes = [];
|
|
66
|
+
const classMap = new Map();
|
|
65
67
|
for (const entityDef of entities) {
|
|
66
68
|
const className = entityDef.name;
|
|
67
69
|
const tableName = (0, casing_1.snakeCase)(entityDef.name);
|
|
@@ -122,9 +124,125 @@ function buildEntityClasses(entities) {
|
|
|
122
124
|
}
|
|
123
125
|
}
|
|
124
126
|
classes.push(EntityClass);
|
|
127
|
+
classMap.set(entityDef.name, EntityClass);
|
|
128
|
+
}
|
|
129
|
+
for (const entityDef of entities) {
|
|
130
|
+
const EntityClass = classMap.get(entityDef.name);
|
|
131
|
+
if (!EntityClass)
|
|
132
|
+
continue;
|
|
133
|
+
const relationships = (0, relationships_1.getRelationshipForTemplate)(entityDef.name, relationshipMap[entityDef.name] || [], entities);
|
|
134
|
+
for (const relationship of relationships) {
|
|
135
|
+
const targetClass = classMap.get(relationship.name);
|
|
136
|
+
if (!targetClass)
|
|
137
|
+
continue;
|
|
138
|
+
const inversePropertyName = relationship.inverseSidePropertyName ||
|
|
139
|
+
relationship.inversePropertyName ||
|
|
140
|
+
entityDef.name;
|
|
141
|
+
const relationOptions = {};
|
|
142
|
+
if (relationship.nullable !== undefined) {
|
|
143
|
+
relationOptions.nullable = relationship.nullable;
|
|
144
|
+
}
|
|
145
|
+
if (relationship.cascadeDelete) {
|
|
146
|
+
relationOptions.onDelete = "CASCADE";
|
|
147
|
+
}
|
|
148
|
+
if (relationship.type === "ManyToOne") {
|
|
149
|
+
if (relationship.index) {
|
|
150
|
+
typeorm.Index()(EntityClass.prototype, relationship.relationshipName);
|
|
151
|
+
}
|
|
152
|
+
const inverseSide = relationship.biDirectional
|
|
153
|
+
? (target) => target[inversePropertyName]
|
|
154
|
+
: undefined;
|
|
155
|
+
typeorm.ManyToOne(() => targetClass, inverseSide, relationOptions)(EntityClass.prototype, relationship.relationshipName);
|
|
156
|
+
typeorm.JoinColumn({ name: relationship.camelCasedId })(EntityClass.prototype, relationship.relationshipName);
|
|
157
|
+
}
|
|
158
|
+
if (relationship.type === "OneToMany" && relationship.biDirectional) {
|
|
159
|
+
typeorm.OneToMany(() => targetClass, (target) => target[inversePropertyName])(EntityClass.prototype, relationship.pluralizedRelationshipName);
|
|
160
|
+
}
|
|
161
|
+
if (relationship.type === "ManyToMany") {
|
|
162
|
+
const inverseSide = relationship.biDirectional
|
|
163
|
+
? (target) => target[inversePropertyName]
|
|
164
|
+
: undefined;
|
|
165
|
+
typeorm.ManyToMany(() => targetClass, inverseSide)(EntityClass.prototype, relationship.pluralizedRelationshipName);
|
|
166
|
+
if (relationship.joinTable) {
|
|
167
|
+
const joinTableOptions = {};
|
|
168
|
+
if (relationship.joinTableName) {
|
|
169
|
+
joinTableOptions.name = relationship.joinTableName;
|
|
170
|
+
}
|
|
171
|
+
if (relationship.joinColumnName) {
|
|
172
|
+
joinTableOptions.joinColumn = {
|
|
173
|
+
name: relationship.joinColumnName,
|
|
174
|
+
referencedColumnName: "id",
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
if (relationship.inverseJoinColumnName) {
|
|
178
|
+
joinTableOptions.inverseJoinColumn = {
|
|
179
|
+
name: relationship.inverseJoinColumnName,
|
|
180
|
+
referencedColumnName: "id",
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
typeorm.JoinTable(Object.keys(joinTableOptions).length > 0
|
|
184
|
+
? joinTableOptions
|
|
185
|
+
: undefined)(EntityClass.prototype, relationship.pluralizedRelationshipName);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (relationship.type === "OneToOne") {
|
|
189
|
+
const inverseSide = relationship.biDirectional
|
|
190
|
+
? (target) => target[inversePropertyName]
|
|
191
|
+
: undefined;
|
|
192
|
+
typeorm.OneToOne(() => targetClass, inverseSide, relationOptions)(EntityClass.prototype, relationship.relationshipName);
|
|
193
|
+
if (relationship.joinTable) {
|
|
194
|
+
typeorm.JoinColumn()(EntityClass.prototype, relationship.relationshipName);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
125
198
|
}
|
|
126
199
|
return classes;
|
|
127
200
|
}
|
|
201
|
+
function snapshotRelationships(relationshipMap) {
|
|
202
|
+
return Object.entries(relationshipMap)
|
|
203
|
+
.flatMap(([from, relationships]) => relationships.map((relationship) => {
|
|
204
|
+
var _a;
|
|
205
|
+
return ({
|
|
206
|
+
from,
|
|
207
|
+
to: relationship.name,
|
|
208
|
+
type: relationship.type,
|
|
209
|
+
to_name: (_a = relationship.referenceName) !== null && _a !== void 0 ? _a : undefined,
|
|
210
|
+
nullable: relationship.nullable,
|
|
211
|
+
bi_directional: relationship.biDirectional,
|
|
212
|
+
cascadeDelete: relationship.cascadeDelete,
|
|
213
|
+
index: relationship.index,
|
|
214
|
+
join: relationship.join,
|
|
215
|
+
joinTableName: relationship.joinTableName,
|
|
216
|
+
joinColumnName: relationship.joinColumnName,
|
|
217
|
+
inverseJoinColumnName: relationship.inverseJoinColumnName,
|
|
218
|
+
inverseReferenceName: relationship.inverseReferenceName,
|
|
219
|
+
});
|
|
220
|
+
}))
|
|
221
|
+
.sort((a, b) => `${a.from}:${a.to}:${a.type}:${a.to_name || ""}`.localeCompare(`${b.from}:${b.to}:${b.type}:${b.to_name || ""}`));
|
|
222
|
+
}
|
|
223
|
+
function relationshipMapFromSnapshot(relationships = []) {
|
|
224
|
+
const relationshipMap = {};
|
|
225
|
+
for (const relationship of relationships) {
|
|
226
|
+
relationshipMap[relationship.from] = [
|
|
227
|
+
...(relationshipMap[relationship.from] || []),
|
|
228
|
+
{
|
|
229
|
+
name: relationship.to,
|
|
230
|
+
type: relationship.type,
|
|
231
|
+
referenceName: relationship.to_name,
|
|
232
|
+
nullable: relationship.nullable,
|
|
233
|
+
biDirectional: relationship.bi_directional,
|
|
234
|
+
cascadeDelete: relationship.cascadeDelete,
|
|
235
|
+
index: relationship.index,
|
|
236
|
+
join: relationship.join,
|
|
237
|
+
joinTableName: relationship.joinTableName,
|
|
238
|
+
joinColumnName: relationship.joinColumnName,
|
|
239
|
+
inverseJoinColumnName: relationship.inverseJoinColumnName,
|
|
240
|
+
inverseReferenceName: relationship.inverseReferenceName,
|
|
241
|
+
},
|
|
242
|
+
];
|
|
243
|
+
}
|
|
244
|
+
return relationshipMap;
|
|
245
|
+
}
|
|
128
246
|
/**
|
|
129
247
|
* Create a TypeORM DataSource configured for PGlite with in-memory entity classes.
|
|
130
248
|
*/
|
|
@@ -185,6 +303,7 @@ async function runMigrationSandbox(current, projectDir) {
|
|
|
185
303
|
scopeBy: e.scopeBy,
|
|
186
304
|
});
|
|
187
305
|
}),
|
|
306
|
+
relationships: snapshotRelationships(current.relationshipMap),
|
|
188
307
|
};
|
|
189
308
|
// Quick check: has anything changed?
|
|
190
309
|
if (snapshot && !schemaChanged(currentSnapshot, snapshot)) {
|
|
@@ -221,7 +340,7 @@ async function runMigrationSandbox(current, projectDir) {
|
|
|
221
340
|
: [];
|
|
222
341
|
// Step 1: Establish baseline with previous schema
|
|
223
342
|
if (previousEntities.length > 0) {
|
|
224
|
-
const prevClasses = buildEntityClasses(previousEntities);
|
|
343
|
+
const prevClasses = buildEntityClasses(previousEntities, relationshipMapFromSnapshot(snapshot === null || snapshot === void 0 ? void 0 : snapshot.relationships));
|
|
225
344
|
const prevDs = await createPGliteDataSource(prevClasses);
|
|
226
345
|
await prevDs.initialize();
|
|
227
346
|
await prevDs.synchronize();
|
|
@@ -230,7 +349,7 @@ async function runMigrationSandbox(current, projectDir) {
|
|
|
230
349
|
// The PGlite in-memory database persists in the singleton.
|
|
231
350
|
}
|
|
232
351
|
// Step 2: Compute diff with current schema
|
|
233
|
-
const currentClasses = buildEntityClasses(current.entities);
|
|
352
|
+
const currentClasses = buildEntityClasses(current.entities, current.relationshipMap);
|
|
234
353
|
const currentDs = await createPGliteDataSource(currentClasses);
|
|
235
354
|
await currentDs.initialize();
|
|
236
355
|
try {
|
|
@@ -314,6 +433,7 @@ function applyMigration(current, projectDir) {
|
|
|
314
433
|
scopeBy: e.scopeBy,
|
|
315
434
|
});
|
|
316
435
|
}),
|
|
436
|
+
relationships: snapshotRelationships(current.relationshipMap),
|
|
317
437
|
};
|
|
318
438
|
(0, snapshot_1.writeSnapshot)(snapshot, projectDir);
|
|
319
439
|
}
|
|
@@ -333,5 +453,7 @@ exports.resetSandbox = resetSandbox;
|
|
|
333
453
|
* Simple deep comparison between current and snapshot schemas.
|
|
334
454
|
*/
|
|
335
455
|
function schemaChanged(current, snapshot) {
|
|
336
|
-
return (JSON.stringify(current.entities) !== JSON.stringify(snapshot.entities)
|
|
456
|
+
return (JSON.stringify(current.entities) !== JSON.stringify(snapshot.entities) ||
|
|
457
|
+
JSON.stringify(current.relationships || []) !==
|
|
458
|
+
JSON.stringify(snapshot.relationships || []));
|
|
337
459
|
}
|
|
@@ -222,19 +222,21 @@ func applyOperator(query *gorm.DB, field, op, value string, useOr bool) *gorm.DB
|
|
|
222
222
|
where = query.Or
|
|
223
223
|
}
|
|
224
224
|
|
|
225
|
+
cv := coerceValue(value)
|
|
226
|
+
|
|
225
227
|
switch op {
|
|
226
228
|
case "$eq":
|
|
227
|
-
return where(fmt.Sprintf("%s = ?", field),
|
|
229
|
+
return where(fmt.Sprintf("%s = ?", field), cv)
|
|
228
230
|
case "$ne":
|
|
229
|
-
return where(fmt.Sprintf("%s != ?", field),
|
|
231
|
+
return where(fmt.Sprintf("%s != ?", field), cv)
|
|
230
232
|
case "$gt":
|
|
231
|
-
return where(fmt.Sprintf("%s > ?", field),
|
|
233
|
+
return where(fmt.Sprintf("%s > ?", field), cv)
|
|
232
234
|
case "$gte":
|
|
233
|
-
return where(fmt.Sprintf("%s >= ?", field),
|
|
235
|
+
return where(fmt.Sprintf("%s >= ?", field), cv)
|
|
234
236
|
case "$lt":
|
|
235
|
-
return where(fmt.Sprintf("%s < ?", field),
|
|
237
|
+
return where(fmt.Sprintf("%s < ?", field), cv)
|
|
236
238
|
case "$lte":
|
|
237
|
-
return where(fmt.Sprintf("%s <= ?", field),
|
|
239
|
+
return where(fmt.Sprintf("%s <= ?", field), cv)
|
|
238
240
|
case "$starts":
|
|
239
241
|
return where(fmt.Sprintf("%s LIKE ?", field), value+"%")
|
|
240
242
|
case "$ends":
|
|
@@ -244,9 +246,9 @@ func applyOperator(query *gorm.DB, field, op, value string, useOr bool) *gorm.DB
|
|
|
244
246
|
case "$excl":
|
|
245
247
|
return where(fmt.Sprintf("%s NOT LIKE ?", field), "%"+value+"%")
|
|
246
248
|
case "$in":
|
|
247
|
-
return where(fmt.Sprintf("%s IN ?", field), splitValue(value))
|
|
249
|
+
return where(fmt.Sprintf("%s IN ?", field), coerceValues(splitValue(value)))
|
|
248
250
|
case "$notin":
|
|
249
|
-
return where(fmt.Sprintf("%s NOT IN ?", field), splitValue(value))
|
|
251
|
+
return where(fmt.Sprintf("%s NOT IN ?", field), coerceValues(splitValue(value)))
|
|
250
252
|
case "$isnull":
|
|
251
253
|
return where(fmt.Sprintf("%s IS NULL", field))
|
|
252
254
|
case "$notnull":
|
|
@@ -254,7 +256,7 @@ func applyOperator(query *gorm.DB, field, op, value string, useOr bool) *gorm.DB
|
|
|
254
256
|
case "$between":
|
|
255
257
|
vals := splitValue(value)
|
|
256
258
|
if len(vals) >= 2 {
|
|
257
|
-
return where(fmt.Sprintf("%s BETWEEN ? AND ?", field), vals[0], vals[1])
|
|
259
|
+
return where(fmt.Sprintf("%s BETWEEN ? AND ?", field), coerceValue(vals[0]), coerceValue(vals[1]))
|
|
258
260
|
}
|
|
259
261
|
return query
|
|
260
262
|
case "$eqL":
|
|
@@ -299,3 +301,29 @@ func splitValue(value string) []string {
|
|
|
299
301
|
}
|
|
300
302
|
return result
|
|
301
303
|
}
|
|
304
|
+
|
|
305
|
+
// coerceValue converts a string query parameter to a typed value.
|
|
306
|
+
// Booleans and numbers are converted so the database driver sends the
|
|
307
|
+
// correct wire type instead of a raw string.
|
|
308
|
+
func coerceValue(value string) interface{} {
|
|
309
|
+
low := strings.ToLower(value)
|
|
310
|
+
if low == "true" || low == "false" || low == "yes" || low == "no" || low == "1" || low == "0" {
|
|
311
|
+
return low == "true" || low == "yes" || low == "1"
|
|
312
|
+
}
|
|
313
|
+
if v, err := strconv.ParseInt(value, 10, 64); err == nil {
|
|
314
|
+
return v
|
|
315
|
+
}
|
|
316
|
+
if v, err := strconv.ParseFloat(value, 64); err == nil {
|
|
317
|
+
return v
|
|
318
|
+
}
|
|
319
|
+
return value
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// coerceValues applies coerceValue to each element in a slice.
|
|
323
|
+
func coerceValues(values []string) []interface{} {
|
|
324
|
+
result := make([]interface{}, len(values))
|
|
325
|
+
for i, v := range values {
|
|
326
|
+
result[i] = coerceValue(v)
|
|
327
|
+
}
|
|
328
|
+
return result
|
|
329
|
+
}
|
|
@@ -6,7 +6,7 @@ import math
|
|
|
6
6
|
from dataclasses import dataclass, field
|
|
7
7
|
from typing import Any, Dict, List, Optional, Sequence, Tuple, Type, TypeVar
|
|
8
8
|
|
|
9
|
-
from sqlalchemy import asc, desc, func, or_
|
|
9
|
+
from sqlalchemy import Boolean, Date, DateTime, Float, Integer, Numeric, asc, desc, func, or_
|
|
10
10
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
11
11
|
from sqlalchemy.orm import selectinload
|
|
12
12
|
from sqlalchemy.sql import Select, select
|
|
@@ -65,30 +65,58 @@ class PaginatedResult:
|
|
|
65
65
|
# Operator registry
|
|
66
66
|
# ---------------------------------------------------------------------------
|
|
67
67
|
|
|
68
|
+
def _coerce_value(col: Any, value: str) -> Any:
|
|
69
|
+
"""Coerce a string query param value to match the column's SQL type."""
|
|
70
|
+
try:
|
|
71
|
+
col_type = col.property.columns[0].type
|
|
72
|
+
except (AttributeError, IndexError):
|
|
73
|
+
return value
|
|
74
|
+
|
|
75
|
+
if isinstance(col_type, Boolean):
|
|
76
|
+
return value.lower() in ("true", "1", "yes")
|
|
77
|
+
if isinstance(col_type, (Integer,)):
|
|
78
|
+
try:
|
|
79
|
+
return int(value)
|
|
80
|
+
except (ValueError, TypeError):
|
|
81
|
+
return value
|
|
82
|
+
if isinstance(col_type, (Float, Numeric)):
|
|
83
|
+
try:
|
|
84
|
+
return float(value)
|
|
85
|
+
except (ValueError, TypeError):
|
|
86
|
+
return value
|
|
87
|
+
return value
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _coerce_list(col: Any, values: List[str]) -> List[Any]:
|
|
91
|
+
"""Coerce a list of string values to match the column's SQL type."""
|
|
92
|
+
return [_coerce_value(col, v) for v in values]
|
|
93
|
+
|
|
94
|
+
|
|
68
95
|
def _apply_operator(model: Any, field_name: str, op: str, value: str) -> Any:
|
|
69
96
|
"""Return a SQLAlchemy filter expression for the given operator."""
|
|
70
97
|
col = getattr(model, field_name, None)
|
|
71
98
|
if col is None:
|
|
72
99
|
return None
|
|
73
100
|
|
|
101
|
+
typed_value = _coerce_value(col, value)
|
|
74
102
|
lower_col = func.lower(col)
|
|
75
103
|
|
|
76
104
|
operators: Dict[str, Any] = {
|
|
77
|
-
"$eq": lambda: col ==
|
|
78
|
-
"$ne": lambda: col !=
|
|
79
|
-
"$gt": lambda: col >
|
|
80
|
-
"$gte": lambda: col >=
|
|
81
|
-
"$lt": lambda: col <
|
|
82
|
-
"$lte": lambda: col <=
|
|
105
|
+
"$eq": lambda: col == typed_value,
|
|
106
|
+
"$ne": lambda: col != typed_value,
|
|
107
|
+
"$gt": lambda: col > typed_value,
|
|
108
|
+
"$gte": lambda: col >= typed_value,
|
|
109
|
+
"$lt": lambda: col < typed_value,
|
|
110
|
+
"$lte": lambda: col <= typed_value,
|
|
83
111
|
"$starts": lambda: col.like(f"{value}%"),
|
|
84
112
|
"$ends": lambda: col.like(f"%{value}"),
|
|
85
113
|
"$cont": lambda: col.like(f"%{value}%"),
|
|
86
114
|
"$excl": lambda: ~col.like(f"%{value}%"),
|
|
87
|
-
"$in": lambda: col.in_(_split_value(value)),
|
|
88
|
-
"$notin": lambda: ~col.in_(_split_value(value)),
|
|
115
|
+
"$in": lambda: col.in_(_coerce_list(col, _split_value(value))),
|
|
116
|
+
"$notin": lambda: ~col.in_(_coerce_list(col, _split_value(value))),
|
|
89
117
|
"$isnull": lambda: col.is_(None),
|
|
90
118
|
"$notnull": lambda: col.isnot(None),
|
|
91
|
-
"$between": lambda: col.between(*_split_value(value)[:2]),
|
|
119
|
+
"$between": lambda: col.between(*_coerce_list(col, _split_value(value)[:2])),
|
|
92
120
|
"$eqL": lambda: lower_col == value.lower(),
|
|
93
121
|
"$neL": lambda: lower_col != value.lower(),
|
|
94
122
|
"$startsL": lambda: lower_col.like(f"{value.lower()}%"),
|
|
@@ -6,7 +6,7 @@ const fs = tslib_1.__importStar(require("fs"));
|
|
|
6
6
|
const path = tslib_1.__importStar(require("path"));
|
|
7
7
|
const shelljs_1 = tslib_1.__importDefault(require("shelljs"));
|
|
8
8
|
exports.TEMPLATE_REPOS = {
|
|
9
|
-
typescript: "https://github.com/apsoai/service-template.git",
|
|
9
|
+
typescript: "https://github.com/apsoai/service-template-ts.git",
|
|
10
10
|
python: "https://github.com/apsoai/service-template-python.git",
|
|
11
11
|
go: "https://github.com/apsoai/service-template-go.git",
|
|
12
12
|
};
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apso/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@apso/cli",
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "0.15.0",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@electric-sql/pglite": "^0.2.17",
|
package/oclif.manifest.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.
|
|
2
|
+
"version": "0.15.0",
|
|
3
3
|
"commands": {
|
|
4
4
|
"config": {
|
|
5
5
|
"id": "config",
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
},
|
|
74
74
|
"dev": {
|
|
75
75
|
"id": "dev",
|
|
76
|
-
"description": "Start the local development server
|
|
76
|
+
"description": "Start the local development server. Detects project language and database type from .apsorc and .env.",
|
|
77
77
|
"strict": true,
|
|
78
78
|
"pluginName": "@apso/cli",
|
|
79
79
|
"pluginAlias": "@apso/cli",
|
|
@@ -81,22 +81,56 @@
|
|
|
81
81
|
"aliases": [],
|
|
82
82
|
"examples": [
|
|
83
83
|
"$ apso dev",
|
|
84
|
-
"$ apso dev --
|
|
85
|
-
"$ apso dev --
|
|
86
|
-
"$ apso dev --build --detach"
|
|
84
|
+
"$ apso dev --docker",
|
|
85
|
+
"$ apso dev --docker --build"
|
|
87
86
|
],
|
|
88
87
|
"flags": {
|
|
88
|
+
"docker": {
|
|
89
|
+
"name": "docker",
|
|
90
|
+
"type": "boolean",
|
|
91
|
+
"description": "Use Docker Compose instead of the native dev stack",
|
|
92
|
+
"allowNo": false
|
|
93
|
+
},
|
|
89
94
|
"detach": {
|
|
90
95
|
"name": "detach",
|
|
91
96
|
"type": "boolean",
|
|
92
97
|
"char": "d",
|
|
93
|
-
"description": "Run containers in the background",
|
|
98
|
+
"description": "Run containers in the background (Docker mode only)",
|
|
94
99
|
"allowNo": false
|
|
95
100
|
},
|
|
96
101
|
"build": {
|
|
97
102
|
"name": "build",
|
|
98
103
|
"type": "boolean",
|
|
99
|
-
"description": "Rebuild images before starting",
|
|
104
|
+
"description": "Rebuild images before starting (Docker mode only)",
|
|
105
|
+
"allowNo": false
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
"args": {}
|
|
109
|
+
},
|
|
110
|
+
"doctor": {
|
|
111
|
+
"id": "doctor",
|
|
112
|
+
"description": "Run diagnostic checks on generated code and optionally file a GitHub issue with findings",
|
|
113
|
+
"strict": true,
|
|
114
|
+
"pluginName": "@apso/cli",
|
|
115
|
+
"pluginAlias": "@apso/cli",
|
|
116
|
+
"pluginType": "core",
|
|
117
|
+
"aliases": [],
|
|
118
|
+
"examples": [
|
|
119
|
+
"$ apso doctor",
|
|
120
|
+
"$ apso doctor --json",
|
|
121
|
+
"$ apso doctor --report"
|
|
122
|
+
],
|
|
123
|
+
"flags": {
|
|
124
|
+
"report": {
|
|
125
|
+
"name": "report",
|
|
126
|
+
"type": "boolean",
|
|
127
|
+
"description": "File a GitHub issue with diagnostic findings (requires gh CLI)",
|
|
128
|
+
"allowNo": false
|
|
129
|
+
},
|
|
130
|
+
"json": {
|
|
131
|
+
"name": "json",
|
|
132
|
+
"type": "boolean",
|
|
133
|
+
"description": "Output findings as JSON instead of human-readable text",
|
|
100
134
|
"allowNo": false
|
|
101
135
|
}
|
|
102
136
|
},
|