@autoappy/cli 0.2.0-beta.0 → 0.3.0-beta.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/README.md +15 -0
- package/package.json +1 -1
- package/src/cli.js +10 -0
- package/src/client.js +2 -0
- package/src/seed.js +48 -0
- package/src/sync.js +1 -1
- package/templates/hosted-app/.vscode/tasks.json +1 -0
- package/templates/hosted-app/README.md +2 -0
- package/templates/hosted-app/autoappy.seed.json +17 -0
- package/templates/hosted-app/package.json +3 -2
package/README.md
CHANGED
|
@@ -35,6 +35,7 @@ autoappy push
|
|
|
35
35
|
autoappy code pull|push
|
|
36
36
|
autoappy db pull|push
|
|
37
37
|
autoappy api pull|push
|
|
38
|
+
autoappy seed push
|
|
38
39
|
autoappy check
|
|
39
40
|
```
|
|
40
41
|
|
|
@@ -42,6 +43,18 @@ autoappy check
|
|
|
42
43
|
|
|
43
44
|
Database synchronization covers schema only. Customer records are never copied into the project or Git by these commands.
|
|
44
45
|
|
|
46
|
+
## Seed a blank app once
|
|
47
|
+
|
|
48
|
+
`autoappy.seed.json` is the complete initial project manifest. It can declare table schemas, fictional or starter records, managed APIs, View grants, and the main View. A View with `"source_directory": "app"` uses the real frontend files instead of embedding them in JSON.
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npm run seed:push
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The CLI sends one plan request, prints the full resource counts and warnings, asks for one confirmation, and applies one atomic backend transaction. The reviewed token is carried internally. The target must be a blank app. Repeating the same apply receipt is idempotent; a different seed never overwrites an initialized app. Afterward, use `db:push`, `api:push`, and `code:push` for ongoing changes.
|
|
55
|
+
|
|
56
|
+
The seed file is for intentional initial fixtures, not a dump of production/customer rows. Normal pull never writes remote records into Git.
|
|
57
|
+
|
|
45
58
|
## Backend contract
|
|
46
59
|
|
|
47
60
|
The Auto Appy development-kit, schema, managed-API, and versioned frontend endpoints are used directly:
|
|
@@ -49,6 +62,8 @@ The Auto Appy development-kit, schema, managed-API, and versioned frontend endpo
|
|
|
49
62
|
```text
|
|
50
63
|
GET /api/v1/platform/organisations/{workspace}/data/apps/{app}/source
|
|
51
64
|
PATCH /api/v1/platform/organisations/{workspace}/data/apps/{app}/source
|
|
65
|
+
POST /api/v1/platform/organisations/{workspace}/data/apps/{app}/seed/plan
|
|
66
|
+
POST /api/v1/platform/organisations/{workspace}/data/apps/{app}/seed/{token}/apply
|
|
52
67
|
```
|
|
53
68
|
|
|
54
69
|
The GET response returns `project`, `version`, `source_hash` and `status`. PATCH accepts `project`, `expected_version`, `source_hash` and `request_id`, then returns a versioned deployment receipt.
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -4,6 +4,7 @@ import { AutoAppyClient } from './client.js';
|
|
|
4
4
|
import { connectionEnvironment } from './env.js';
|
|
5
5
|
import { hash, readConfig, readLocalProject, readState } from './files.js';
|
|
6
6
|
import { initialize } from './init.js';
|
|
7
|
+
import { pushSeed } from './seed.js';
|
|
7
8
|
import { pullCode, pullDataDefinitions, pushApis, pushCode, pushSchema } from './sync.js';
|
|
8
9
|
|
|
9
10
|
function help() {
|
|
@@ -22,6 +23,7 @@ Usage: autoappy <command>
|
|
|
22
23
|
db push Review and apply database schema changes
|
|
23
24
|
api pull Pull managed API definitions
|
|
24
25
|
api push Push managed API definitions
|
|
26
|
+
seed push Seed a blank app from one reviewed JSON manifest
|
|
25
27
|
check Validate hosted frontend files
|
|
26
28
|
`);
|
|
27
29
|
}
|
|
@@ -88,6 +90,14 @@ export async function run(args, options = {}) {
|
|
|
88
90
|
return;
|
|
89
91
|
}
|
|
90
92
|
if (command === 'api' && subcommand === 'push') return console.log(`Saved ${(await pushApis({ root, client, config })).saved} API definition(s).`);
|
|
93
|
+
if (command === 'seed' && subcommand === 'push') {
|
|
94
|
+
const result = await pushSeed({ root, client, yes });
|
|
95
|
+
if (!result.applied) return console.log('Initial seed was not applied.');
|
|
96
|
+
const code = await pullCode({ root, client });
|
|
97
|
+
const definitions = await pullDataDefinitions({ root, client });
|
|
98
|
+
console.log(`Seed complete: ${result.receipt.tables.length} table(s), ${result.receipt.record_count} record(s), ${result.receipt.apis.length} API(s), ${result.receipt.views.length} View(s); synchronized ${code.files} frontend file(s) and ${definitions.apis} API definition(s).`);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
91
101
|
if (command === 'pull') {
|
|
92
102
|
const code = await pullCode({ root, client });
|
|
93
103
|
const definitions = await pullDataDefinitions({ root, client });
|
package/src/client.js
CHANGED
|
@@ -38,6 +38,8 @@ export class AutoAppyClient {
|
|
|
38
38
|
saveSource(payload) { return this.request('PATCH', `apps/${encodeURIComponent(this.appId)}/source`, payload); }
|
|
39
39
|
planSchema(schema) { return this.request('POST', `apps/${encodeURIComponent(this.appId)}/schema/plan`, { schema }); }
|
|
40
40
|
applySchema(token, reviewHash) { return this.request('POST', `apps/${encodeURIComponent(this.appId)}/schema/${encodeURIComponent(token)}/apply`, { review_hash: reviewHash }); }
|
|
41
|
+
planSeed(seed) { return this.request('POST', `apps/${encodeURIComponent(this.appId)}/seed/plan`, { seed }); }
|
|
42
|
+
applySeed(token, reviewHash) { return this.request('POST', `apps/${encodeURIComponent(this.appId)}/seed/${encodeURIComponent(token)}/apply`, { review_hash: reviewHash }); }
|
|
41
43
|
createApi(payload) { return this.request('POST', 'api', payload); }
|
|
42
44
|
updateApi(id, payload) { return this.request('PATCH', `api/${encodeURIComponent(id)}`, payload); }
|
|
43
45
|
}
|
package/src/seed.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { json, readJson, readLocalProject, readState, writeLocalProject, writeState } from './files.js';
|
|
4
|
+
import { confirmed } from './sync.js';
|
|
5
|
+
|
|
6
|
+
export function readSeed(root) {
|
|
7
|
+
const filename = path.join(root, 'autoappy.seed.json');
|
|
8
|
+
const seed = readJson(filename);
|
|
9
|
+
if (!seed || seed.format !== 'autoappy.app-seed' || seed.version !== 1 || !Array.isArray(seed.tables) || !Array.isArray(seed.apis) || !Array.isArray(seed.views)) {
|
|
10
|
+
throw new Error('Create a valid autoappy.seed.json before running seed push.');
|
|
11
|
+
}
|
|
12
|
+
if (fs.statSync(filename).size > 8_000_000) throw new Error('Keep autoappy.seed.json within 8 MB.');
|
|
13
|
+
const views = seed.views.map(view => {
|
|
14
|
+
if (!view || typeof view !== 'object' || Array.isArray(view)) throw new Error('Every seed View must be a JSON object.');
|
|
15
|
+
if (view.source_directory !== undefined) {
|
|
16
|
+
if (view.source_directory !== 'app' || view.source !== undefined) throw new Error('A seed View may use source_directory: "app" or source, not both.');
|
|
17
|
+
const { source_directory, ...definition } = view;
|
|
18
|
+
return { ...definition, source: JSON.stringify(readLocalProject(root)) };
|
|
19
|
+
}
|
|
20
|
+
if (typeof view.source !== 'string') throw new Error('Every seed View needs source_directory: "app" or a source string.');
|
|
21
|
+
return view;
|
|
22
|
+
});
|
|
23
|
+
return { ...seed, views };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function resolveApiPlaceholders(project, apis) {
|
|
27
|
+
const ids = Object.fromEntries((apis || []).map(api => [api.key, api.id]));
|
|
28
|
+
return { ...project, files: project.files.map(file => ({ ...file, content: file.content.replace(/\{\{api:([a-z][a-z0-9_]{0,40})\}\}/g, (match, key) => ids[key] || match) })) };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function pushSeed({ root, client, yes = false }) {
|
|
32
|
+
const seed = readSeed(root);
|
|
33
|
+
const plan = await client.planSeed(seed);
|
|
34
|
+
if (!plan?.token || !plan.review_hash || !Array.isArray(plan.tables)) throw new Error('Auto Appy returned an invalid app seed review.');
|
|
35
|
+
console.log(json({ tables: plan.tables, records: plan.record_count, apis: plan.api_count, views: plan.view_count, main_view: plan.main_view, warnings: plan.warnings || [] }).trim());
|
|
36
|
+
if (!await confirmed(`Apply this initial seed with ${plan.record_count} record(s)?`, yes)) return { applied: false, plan };
|
|
37
|
+
const receipt = await client.applySeed(plan.token, plan.review_hash);
|
|
38
|
+
if (!receipt?.database_id || !Array.isArray(receipt.tables) || !Array.isArray(receipt.apis) || !Array.isArray(receipt.views)) {
|
|
39
|
+
throw new Error('Auto Appy returned an incomplete seed receipt. Check the app before retrying.');
|
|
40
|
+
}
|
|
41
|
+
const local = readLocalProject(root);
|
|
42
|
+
const resolved = resolveApiPlaceholders(local, receipt.apis);
|
|
43
|
+
writeLocalProject(root, resolved);
|
|
44
|
+
const state = readState(root);
|
|
45
|
+
state.seed = { appliedAt: new Date().toISOString(), reviewHash: plan.review_hash, receipt };
|
|
46
|
+
writeState(root, state);
|
|
47
|
+
return { applied: true, plan, receipt };
|
|
48
|
+
}
|
package/src/sync.js
CHANGED
|
@@ -111,7 +111,7 @@ export async function pullDataDefinitions({ root, client, includeSchema = true,
|
|
|
111
111
|
return { database: kit.database.id, apis: includeApis ? kit.apis.length : 0, schema: includeSchema && Boolean(kit.schema) };
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
-
async function confirmed(question, yes) {
|
|
114
|
+
export async function confirmed(question, yes) {
|
|
115
115
|
if (yes) return true;
|
|
116
116
|
if (!process.stdin.isTTY) throw new Error('Database changes require an interactive confirmation. CI may use --yes after reviewing the commit.');
|
|
117
117
|
const prompt = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
"tasks": [
|
|
4
4
|
{ "label": "Auto Appy: Start", "type": "npm", "script": "dev", "isBackground": true, "problemMatcher": [] },
|
|
5
5
|
{ "label": "Auto Appy: Status", "type": "npm", "script": "status", "problemMatcher": [] },
|
|
6
|
+
{ "label": "Auto Appy: Seed blank app", "type": "npm", "script": "seed:push", "problemMatcher": [] },
|
|
6
7
|
{ "label": "Auto Appy: Pull", "type": "npm", "script": "pull", "problemMatcher": [] },
|
|
7
8
|
{ "label": "Auto Appy: Push", "type": "npm", "script": "push", "problemMatcher": [] }
|
|
8
9
|
]
|
|
@@ -8,3 +8,5 @@
|
|
|
8
8
|
|
|
9
9
|
Database commands synchronize schema definitions, not customer records.
|
|
10
10
|
The first pull keeps this starter when the Auto Appy app has no frontend yet. The first push creates and publishes it.
|
|
11
|
+
|
|
12
|
+
For a completely blank app, edit `autoappy.seed.json` to declare initial table schemas, starter rows, managed APIs, View connections, and the main View. Keep `source_directory: "app"` to use the real files in `app/`, then run `npm run seed:push`. It reviews and commits the whole initial project atomically. Use normal pull/push commands after the seed succeeds.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"format": "autoappy.app-seed",
|
|
3
|
+
"version": 1,
|
|
4
|
+
"main_view": "app",
|
|
5
|
+
"tables": [],
|
|
6
|
+
"apis": [],
|
|
7
|
+
"views": [
|
|
8
|
+
{
|
|
9
|
+
"key": "app",
|
|
10
|
+
"name": "Application frontend",
|
|
11
|
+
"source_directory": "app",
|
|
12
|
+
"tables": [],
|
|
13
|
+
"apis": [],
|
|
14
|
+
"allow_writes": false
|
|
15
|
+
}
|
|
16
|
+
]
|
|
17
|
+
}
|
|
@@ -14,14 +14,15 @@
|
|
|
14
14
|
"db:pull": "autoappy db pull",
|
|
15
15
|
"db:push": "autoappy db push",
|
|
16
16
|
"api:pull": "autoappy api pull",
|
|
17
|
-
"api:push": "autoappy api push"
|
|
17
|
+
"api:push": "autoappy api push",
|
|
18
|
+
"seed:push": "autoappy seed push"
|
|
18
19
|
},
|
|
19
20
|
"dependencies": {
|
|
20
21
|
"react": "18.3.1",
|
|
21
22
|
"react-dom": "18.3.1"
|
|
22
23
|
},
|
|
23
24
|
"devDependencies": {
|
|
24
|
-
"@autoappy/cli": "0.
|
|
25
|
+
"@autoappy/cli": "0.3.0-beta.0",
|
|
25
26
|
"vite": "6.1.0"
|
|
26
27
|
},
|
|
27
28
|
"engines": {
|