nsa-sheets-db-builder 4.0.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/LICENSE +21 -0
- package/README.md +188 -0
- package/bin/sheets-deployer.mjs +169 -0
- package/libs/alasql.js +15577 -0
- package/libs/common/gas_response_helper.ts +147 -0
- package/libs/common/gaserror.ts +101 -0
- package/libs/common/gaslogger.ts +172 -0
- package/libs/db_ddl.ts +316 -0
- package/libs/libraries.json +56 -0
- package/libs/spreadsheets_db.ts +4406 -0
- package/libs/triggers.ts +113 -0
- package/package.json +73 -0
- package/scripts/build.mjs +513 -0
- package/scripts/clean.mjs +31 -0
- package/scripts/create.mjs +94 -0
- package/scripts/ddl-handler.mjs +232 -0
- package/scripts/describe.mjs +38 -0
- package/scripts/drop.mjs +39 -0
- package/scripts/init.mjs +465 -0
- package/scripts/lib/utils.mjs +1019 -0
- package/scripts/login.mjs +102 -0
- package/scripts/provision.mjs +35 -0
- package/scripts/refresh-cache.mjs +34 -0
- package/scripts/set-key.mjs +48 -0
- package/scripts/setup-trigger.mjs +95 -0
- package/scripts/setup.mjs +677 -0
- package/scripts/show.mjs +37 -0
- package/scripts/sync.mjs +35 -0
- package/scripts/whoami.mjs +36 -0
- package/src/api/ddl-handler-entry.ts +136 -0
- package/src/api/ddl.ts +321 -0
- package/src/templates/.clasp.json.ejs +1 -0
- package/src/templates/appsscript.json.ejs +16 -0
- package/src/templates/config.ts.ejs +14 -0
- package/src/templates/ddl-handler-config.ts.ejs +3 -0
- package/src/templates/ddl-handler-main.ts.ejs +56 -0
- package/src/templates/main.ts.ejs +288 -0
- package/src/templates/rbac.ts.ejs +148 -0
- package/src/templates/views.ts.ejs +92 -0
- package/templates/blank.json +33 -0
- package/templates/blog-cms.json +507 -0
- package/templates/crm.json +360 -0
- package/templates/e-commerce.json +424 -0
- package/templates/inventory.json +307 -0
package/scripts/init.mjs
ADDED
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Initialize a new DB project from a template.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* node scripts/init.mjs --db <name>
|
|
8
|
+
* node scripts/init.mjs --db <name> --template <template>
|
|
9
|
+
* node scripts/init.mjs --db <name> --instances r,w
|
|
10
|
+
* node scripts/init.mjs --db <name> --auth simple --rbac
|
|
11
|
+
* node scripts/init.mjs --db <name> --views
|
|
12
|
+
*
|
|
13
|
+
* Interactive: prompts for template selection if --template not given.
|
|
14
|
+
* Non-interactive: use --template to skip the prompt.
|
|
15
|
+
*
|
|
16
|
+
* Flags:
|
|
17
|
+
* --instances Comma-separated instance types: rw, r, w (or read-write, read-only, write-only)
|
|
18
|
+
* Default: single read-write (rw) instance
|
|
19
|
+
* --auth Authentication mode: noAuth, simple, google
|
|
20
|
+
* noAuth — no authentication, audit "who" = "system"
|
|
21
|
+
* simple — user email + passphrase in request body
|
|
22
|
+
* google — Google Workspace SSO (Session.getActiveUser())
|
|
23
|
+
* Default: noAuth
|
|
24
|
+
* --rbac Enable role-based access control. Scaffolds rbac.json + users/roles tables.
|
|
25
|
+
* Requires --auth simple or --auth google.
|
|
26
|
+
* --views Enable SQL views support. Scaffolds views.json.
|
|
27
|
+
*
|
|
28
|
+
* Creates:
|
|
29
|
+
* dbs/<name>/project.json — all config (name, settings, authMode, features, environments)
|
|
30
|
+
* dbs/<name>/tables.json — table definitions (from template)
|
|
31
|
+
* dbs/<name>/libs/ — scaffolded library source files (.ts + .js)
|
|
32
|
+
* dbs/<name>/methods/ — custom method handlers (empty scaffold)
|
|
33
|
+
* dbs/<name>/rbac.json — RBAC config (if --rbac)
|
|
34
|
+
* dbs/<name>/views.json — SQL view definitions (if --views)
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
import fs from 'fs';
|
|
38
|
+
import path from 'path';
|
|
39
|
+
import readline from 'readline';
|
|
40
|
+
import {
|
|
41
|
+
parseArgs, PACKAGE_ROOT, DBS_DIR, COMMON_LIBS_DIR,
|
|
42
|
+
getEnvDefaults, resolveInstanceType,
|
|
43
|
+
loadRootConfig, saveRootConfig
|
|
44
|
+
} from './lib/utils.mjs';
|
|
45
|
+
|
|
46
|
+
const TEMPLATES_DIR = path.join(PACKAGE_ROOT, 'templates');
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Library scaffold order — dependency-aware.
|
|
50
|
+
* Each entry: { name, file, source }
|
|
51
|
+
* source: 'local' = libs/ in package, 'common' = common/libs/
|
|
52
|
+
*/
|
|
53
|
+
const LIB_SCAFFOLD_ORDER = [
|
|
54
|
+
{ name: 'gaserror', file: 'gaserror.ts', source: 'common' },
|
|
55
|
+
{ name: 'gaslogger', file: 'gaslogger.ts', source: 'common' },
|
|
56
|
+
{ name: 'gas_response_helper', file: 'gas_response_helper.ts', source: 'common' },
|
|
57
|
+
{ name: 'spreadsheets_db', file: 'spreadsheets_db.ts', source: 'local' },
|
|
58
|
+
{ name: 'db_ddl', file: 'db_ddl.ts', source: 'local' },
|
|
59
|
+
{ name: 'triggers', file: 'triggers.ts', source: 'local' },
|
|
60
|
+
{ name: 'alasql', file: 'alasql.js', source: 'local', native: true }
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
function loadTemplates() {
|
|
64
|
+
const files = fs.readdirSync(TEMPLATES_DIR).filter(f => f.endsWith('.json'));
|
|
65
|
+
const templates = [];
|
|
66
|
+
for (const file of files) {
|
|
67
|
+
const data = JSON.parse(fs.readFileSync(path.join(TEMPLATES_DIR, file), 'utf8'));
|
|
68
|
+
templates.push(data);
|
|
69
|
+
}
|
|
70
|
+
return templates;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function ask(rl, question) {
|
|
74
|
+
return new Promise(resolve => rl.question(question, resolve));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Scaffold library .ts files into dbs/<name>/libs/ with numeric prefixes.
|
|
79
|
+
* Files are copied from the package — user can edit them afterward.
|
|
80
|
+
*/
|
|
81
|
+
function scaffoldLibs(libsDir) {
|
|
82
|
+
fs.mkdirSync(libsDir, { recursive: true });
|
|
83
|
+
let copied = 0;
|
|
84
|
+
|
|
85
|
+
for (let i = 0; i < LIB_SCAFFOLD_ORDER.length; i++) {
|
|
86
|
+
const lib = LIB_SCAFFOLD_ORDER[i];
|
|
87
|
+
const srcDir = lib.source === 'common'
|
|
88
|
+
? COMMON_LIBS_DIR
|
|
89
|
+
: path.join(PACKAGE_ROOT, 'libs');
|
|
90
|
+
const srcPath = path.join(srcDir, lib.file);
|
|
91
|
+
|
|
92
|
+
if (!fs.existsSync(srcPath)) {
|
|
93
|
+
console.warn(` Warning: ${lib.file} not found at ${srcPath}, skipping.`);
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const ext = lib.native ? path.extname(lib.file) : '.ts';
|
|
98
|
+
const destName = `${String(i).padStart(3, '0')}_${lib.name}${ext}`;
|
|
99
|
+
fs.copyFileSync(srcPath, path.join(libsDir, destName));
|
|
100
|
+
console.log(` ${destName}`);
|
|
101
|
+
copied++;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return copied;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Resolve instance types from --instances flag.
|
|
109
|
+
* Accepts short aliases: rw, r, w (or full names).
|
|
110
|
+
* Default: single read-write instance.
|
|
111
|
+
*/
|
|
112
|
+
function resolveInstances(args) {
|
|
113
|
+
const instances = {};
|
|
114
|
+
|
|
115
|
+
if (args.instances && typeof args.instances === 'string') {
|
|
116
|
+
const inputs = args.instances.split(',').map(s => s.trim()).filter(Boolean);
|
|
117
|
+
const types = inputs.map(resolveInstanceType);
|
|
118
|
+
for (const type of types) {
|
|
119
|
+
const placeholder = types.length === 1 ? 'YOUR_SCRIPT_ID' : `YOUR_${type.toUpperCase().replace(/-/g, '_')}_SCRIPT_ID`;
|
|
120
|
+
instances[placeholder] = {
|
|
121
|
+
type,
|
|
122
|
+
deploymentId: ''
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
} else {
|
|
126
|
+
instances['YOUR_SCRIPT_ID'] = {
|
|
127
|
+
type: 'read-write',
|
|
128
|
+
deploymentId: ''
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return instances;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function generateProjectJson(dbName, instances, envDefaults = {}, features = {}, authMode = 'noAuth') {
|
|
136
|
+
const project = {
|
|
137
|
+
name: dbName,
|
|
138
|
+
settings: {
|
|
139
|
+
loggingVerbosity: envDefaults.loggingVerbosity ?? 2
|
|
140
|
+
},
|
|
141
|
+
authMode,
|
|
142
|
+
environments: {
|
|
143
|
+
dev: {
|
|
144
|
+
driveFolderId: envDefaults.driveFolderId || '',
|
|
145
|
+
systemSpreadsheetId: '',
|
|
146
|
+
instances
|
|
147
|
+
},
|
|
148
|
+
prod: {
|
|
149
|
+
driveFolderId: '',
|
|
150
|
+
systemSpreadsheetId: '',
|
|
151
|
+
instances: {}
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
// Only add features block if any feature is enabled
|
|
157
|
+
if (features.rbac || features.views) {
|
|
158
|
+
project.features = {};
|
|
159
|
+
if (features.rbac) {
|
|
160
|
+
project.features.rbac = { enabled: true };
|
|
161
|
+
}
|
|
162
|
+
if (features.views) {
|
|
163
|
+
project.features.views = { enabled: true };
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return JSON.stringify(project, null, 2) + '\n';
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function generateTablesJson(tables) {
|
|
171
|
+
// Ensure every table has a spreadsheetId (default __new__ for creation)
|
|
172
|
+
const result = {};
|
|
173
|
+
for (const [name, table] of Object.entries(tables)) {
|
|
174
|
+
result[name] = {
|
|
175
|
+
spreadsheetId: '__new__',
|
|
176
|
+
...table
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
return JSON.stringify(result, null, 2) + '\n';
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** RBAC system tables — injected when --rbac is used */
|
|
183
|
+
const RBAC_TABLES = {
|
|
184
|
+
users: {
|
|
185
|
+
spreadsheetId: '__new__',
|
|
186
|
+
sheetName: 'users',
|
|
187
|
+
schema: {
|
|
188
|
+
id: { type: 'string', primaryKey: true, required: true },
|
|
189
|
+
email: { type: 'string', required: true },
|
|
190
|
+
name: { type: 'string', required: true },
|
|
191
|
+
role_id: { type: 'string', foreignKey: 'roles.id' },
|
|
192
|
+
role: { type: 'string' },
|
|
193
|
+
active: { type: 'boolean', defaultValue: true },
|
|
194
|
+
__audit__: { type: 'object' },
|
|
195
|
+
__archived__: { type: 'boolean', defaultValue: false }
|
|
196
|
+
},
|
|
197
|
+
idGenerator: 'UUID',
|
|
198
|
+
deleteMode: 'soft'
|
|
199
|
+
},
|
|
200
|
+
roles: {
|
|
201
|
+
spreadsheetId: '__new__',
|
|
202
|
+
sheetName: 'roles',
|
|
203
|
+
schema: {
|
|
204
|
+
id: { type: 'string', primaryKey: true, required: true },
|
|
205
|
+
name: { type: 'string', required: true },
|
|
206
|
+
description: { type: 'string' },
|
|
207
|
+
__audit__: { type: 'object' },
|
|
208
|
+
__archived__: { type: 'boolean', defaultValue: false }
|
|
209
|
+
},
|
|
210
|
+
idGenerator: 'UUID',
|
|
211
|
+
deleteMode: 'soft'
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
function generateRbacJson() {
|
|
216
|
+
return JSON.stringify({
|
|
217
|
+
roles: {
|
|
218
|
+
admin: {
|
|
219
|
+
description: 'Full access to all tables, views, and methods',
|
|
220
|
+
capabilities: ['*:*', '*']
|
|
221
|
+
},
|
|
222
|
+
publisher: {
|
|
223
|
+
description: 'Can create and edit content — no structural changes',
|
|
224
|
+
capabilities: [
|
|
225
|
+
'*:list', '*:get',
|
|
226
|
+
'pages:create', 'pages:update',
|
|
227
|
+
'entries:create', 'entries:update',
|
|
228
|
+
'categories:create', 'categories:update',
|
|
229
|
+
'tags:create', 'tags:update',
|
|
230
|
+
'media:create', 'media:update',
|
|
231
|
+
'view:*',
|
|
232
|
+
'getStats'
|
|
233
|
+
],
|
|
234
|
+
filter: {
|
|
235
|
+
entries: 'author_id'
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
viewer: {
|
|
239
|
+
description: 'Read-only access — use views for filtered content',
|
|
240
|
+
capabilities: ['*:list', '*:get', 'view:publishedEntries', 'view:entriesByCategory']
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
defaultRole: 'viewer'
|
|
244
|
+
}, null, 2) + '\n';
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function generateViewsJson() {
|
|
248
|
+
return JSON.stringify({}, null, 2) + '\n';
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function generateCustomMethodsJson() {
|
|
252
|
+
return JSON.stringify({
|
|
253
|
+
getStats: {
|
|
254
|
+
handler: 'getStats.ts',
|
|
255
|
+
description: 'Example — get row counts for all tables'
|
|
256
|
+
}
|
|
257
|
+
}, null, 2) + '\n';
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Scaffold the overrides/ directory with an example custom method handler.
|
|
262
|
+
*/
|
|
263
|
+
function scaffoldExampleOverride(overridesDir) {
|
|
264
|
+
fs.mkdirSync(overridesDir, { recursive: true });
|
|
265
|
+
|
|
266
|
+
const exampleMethod = `/**
|
|
267
|
+
* Custom method handler: getStats
|
|
268
|
+
*
|
|
269
|
+
* Convention: export a function named handle_<actionName> that receives:
|
|
270
|
+
* - body: parsed request body
|
|
271
|
+
* - db: DBV2 instance
|
|
272
|
+
* - logger: logger instance
|
|
273
|
+
* - user: (optional, if RBAC enabled) { email, role, id }
|
|
274
|
+
*
|
|
275
|
+
* Must return a GoogleAppsScript.Content.TextOutput (use jsonResponse()).
|
|
276
|
+
*
|
|
277
|
+
* Declared in customMethods.json, handler files live in overrides/.
|
|
278
|
+
* Request: POST { "action": "getStats", "apiKey": "..." }
|
|
279
|
+
*/
|
|
280
|
+
function handle_getStats(body: any, db: any, logger: any, user?: any): GoogleAppsScript.Content.TextOutput {
|
|
281
|
+
// Example: count rows in all tables
|
|
282
|
+
const schema = typeof DB_SCHEMA !== 'undefined' ? DB_SCHEMA : {};
|
|
283
|
+
const stats: Record<string, number> = {};
|
|
284
|
+
|
|
285
|
+
for (const tableName of Object.keys(schema)) {
|
|
286
|
+
const table = db.table(tableName);
|
|
287
|
+
if (table) {
|
|
288
|
+
const result = table.list({});
|
|
289
|
+
stats[tableName] = result?.data?.length || 0;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return jsonResponse({ stats });
|
|
294
|
+
}
|
|
295
|
+
`;
|
|
296
|
+
fs.writeFileSync(path.join(overridesDir, 'getStats.ts'), exampleMethod);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
async function main() {
|
|
300
|
+
const args = parseArgs();
|
|
301
|
+
|
|
302
|
+
if (!args.db) {
|
|
303
|
+
console.error('Usage: node scripts/init.mjs --db <name> [--template <template>] [--instances rw,r,w] [--auth noAuth|simple|google] [--rbac] [--views]');
|
|
304
|
+
process.exit(1);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const dbName = args.db;
|
|
308
|
+
|
|
309
|
+
if (!/^[a-z0-9][a-z0-9_-]*$/i.test(dbName)) {
|
|
310
|
+
console.error(`Invalid DB name: "${dbName}"`);
|
|
311
|
+
console.error('Must contain only letters, numbers, hyphens, and underscores.');
|
|
312
|
+
process.exit(1);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const dbDir = path.join(DBS_DIR, dbName);
|
|
316
|
+
if (fs.existsSync(dbDir)) {
|
|
317
|
+
console.error(`DB project "${dbName}" already exists at ${dbDir}`);
|
|
318
|
+
process.exit(1);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const templates = loadTemplates();
|
|
322
|
+
let selectedTemplate;
|
|
323
|
+
|
|
324
|
+
if (args.template) {
|
|
325
|
+
selectedTemplate = templates.find(t => t.name === args.template);
|
|
326
|
+
if (!selectedTemplate) {
|
|
327
|
+
console.error(`Template "${args.template}" not found.`);
|
|
328
|
+
console.error(`Available templates: ${templates.map(t => t.name).join(', ')}`);
|
|
329
|
+
process.exit(1);
|
|
330
|
+
}
|
|
331
|
+
} else {
|
|
332
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
333
|
+
|
|
334
|
+
console.log('\nAvailable templates:\n');
|
|
335
|
+
templates.forEach((t, i) => {
|
|
336
|
+
const tableCount = Object.keys(t.tables).length;
|
|
337
|
+
console.log(` ${i + 1}. ${t.name} — ${t.description} (${tableCount} tables)`);
|
|
338
|
+
});
|
|
339
|
+
console.log('');
|
|
340
|
+
|
|
341
|
+
const answer = await ask(rl, `Select template (1-${templates.length}): `);
|
|
342
|
+
rl.close();
|
|
343
|
+
|
|
344
|
+
const idx = parseInt(answer, 10) - 1;
|
|
345
|
+
if (isNaN(idx) || idx < 0 || idx >= templates.length) {
|
|
346
|
+
console.error('Invalid selection.');
|
|
347
|
+
process.exit(1);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
selectedTemplate = templates[idx];
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const instances = resolveInstances(args);
|
|
354
|
+
const envDefaults = getEnvDefaults('dev');
|
|
355
|
+
|
|
356
|
+
// Resolve auth mode
|
|
357
|
+
const VALID_AUTH_MODES = ['noAuth', 'simple', 'google'];
|
|
358
|
+
const authMode = (typeof args.auth === 'string') ? args.auth : 'noAuth';
|
|
359
|
+
if (!VALID_AUTH_MODES.includes(authMode)) {
|
|
360
|
+
console.error(`Invalid auth mode: "${authMode}". Use: ${VALID_AUTH_MODES.join(', ')}`);
|
|
361
|
+
process.exit(1);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Resolve feature flags
|
|
365
|
+
const features = {};
|
|
366
|
+
if (args.rbac) {
|
|
367
|
+
if (authMode === 'noAuth') {
|
|
368
|
+
console.error('RBAC requires --auth simple or --auth google (cannot use noAuth with RBAC).');
|
|
369
|
+
process.exit(1);
|
|
370
|
+
}
|
|
371
|
+
features.rbac = true;
|
|
372
|
+
}
|
|
373
|
+
if (args.views) {
|
|
374
|
+
features.views = true;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Create the DB project directory
|
|
378
|
+
fs.mkdirSync(dbDir, { recursive: true });
|
|
379
|
+
|
|
380
|
+
// Merge RBAC tables into template tables if RBAC enabled
|
|
381
|
+
let tables = { ...selectedTemplate.tables };
|
|
382
|
+
if (features.rbac) {
|
|
383
|
+
tables = { ...RBAC_TABLES, ...tables };
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Write config files
|
|
387
|
+
fs.writeFileSync(path.join(dbDir, 'project.json'), generateProjectJson(dbName, instances, envDefaults, features, authMode));
|
|
388
|
+
fs.writeFileSync(path.join(dbDir, 'tables.json'), generateTablesJson(tables));
|
|
389
|
+
|
|
390
|
+
// RBAC config
|
|
391
|
+
if (features.rbac) {
|
|
392
|
+
fs.writeFileSync(path.join(dbDir, 'rbac.json'), generateRbacJson());
|
|
393
|
+
console.log(' Created rbac.json (3 default roles: admin, publisher, viewer)');
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Views config
|
|
397
|
+
if (features.views) {
|
|
398
|
+
fs.writeFileSync(path.join(dbDir, 'views.json'), generateViewsJson());
|
|
399
|
+
console.log(' Created views.json (empty — add views after setup)');
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Custom methods config — always created with example
|
|
403
|
+
fs.writeFileSync(path.join(dbDir, 'customMethods.json'), generateCustomMethodsJson());
|
|
404
|
+
console.log(' Created customMethods.json (1 example: getStats)');
|
|
405
|
+
|
|
406
|
+
// Scaffold library files
|
|
407
|
+
const libsDir = path.join(dbDir, 'libs');
|
|
408
|
+
console.log('\n Scaffolding libraries:');
|
|
409
|
+
const libCount = scaffoldLibs(libsDir);
|
|
410
|
+
|
|
411
|
+
// Scaffold overrides directory with example handler
|
|
412
|
+
const overridesDir = path.join(dbDir, 'overrides');
|
|
413
|
+
scaffoldExampleOverride(overridesDir);
|
|
414
|
+
console.log('\n Scaffolding overrides:');
|
|
415
|
+
console.log(' overrides/getStats.ts (example custom method handler)');
|
|
416
|
+
|
|
417
|
+
// Register project in .nsaproject.json
|
|
418
|
+
const rootConfig = loadRootConfig();
|
|
419
|
+
if (!rootConfig.kind) rootConfig.kind = 'db';
|
|
420
|
+
if (!rootConfig.environments) rootConfig.environments = {};
|
|
421
|
+
if (!rootConfig.projects) rootConfig.projects = [];
|
|
422
|
+
|
|
423
|
+
// Ensure env entries exist
|
|
424
|
+
for (const envName of ['dev', 'prod']) {
|
|
425
|
+
if (!rootConfig.environments[envName]) {
|
|
426
|
+
rootConfig.environments[envName] = {
|
|
427
|
+
account: '',
|
|
428
|
+
projectId: '',
|
|
429
|
+
ddlHandler: { scriptId: '', deploymentId: '', ddlAdminKey: '' }
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Store account from env defaults if available and not already set
|
|
435
|
+
if (envDefaults.account && !rootConfig.environments.dev.account) {
|
|
436
|
+
rootConfig.environments.dev.account = envDefaults.account;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Add project to registry if not already listed
|
|
440
|
+
if (!rootConfig.projects.includes(dbName)) {
|
|
441
|
+
rootConfig.projects.push(dbName);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
saveRootConfig(rootConfig);
|
|
445
|
+
|
|
446
|
+
const tableNames = Object.keys(tables);
|
|
447
|
+
const instanceKeys = Object.keys(instances);
|
|
448
|
+
console.log(`\nCreated DB project: ${dbName}`);
|
|
449
|
+
console.log(` Template: ${selectedTemplate.name}`);
|
|
450
|
+
console.log(` Tables: ${tableNames.join(', ')}`);
|
|
451
|
+
console.log(` Libs: ${libCount} files scaffolded`);
|
|
452
|
+
console.log(` Instances: ${instanceKeys.join(', ')}`);
|
|
453
|
+
console.log(` Auth: ${authMode}`);
|
|
454
|
+
if (features.rbac) console.log(` RBAC: enabled`);
|
|
455
|
+
if (features.views) console.log(` Views: enabled`);
|
|
456
|
+
console.log(` Path: ${dbDir}`);
|
|
457
|
+
console.log(` Registered in .nsaproject.json`);
|
|
458
|
+
console.log(`\nNext steps:`);
|
|
459
|
+
console.log(` 1. nsa-sheets-db-builder login --db ${dbName}`);
|
|
460
|
+
console.log(` 2. nsa-sheets-db-builder setup --db ${dbName}`);
|
|
461
|
+
if (features.rbac) console.log(`\n Optional: edit dbs/${dbName}/rbac.json before setup`);
|
|
462
|
+
if (features.views) console.log(` Optional: edit dbs/${dbName}/views.json before setup`);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
main();
|