@construct-space/cli 1.0.2 → 1.0.4
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/index.js
CHANGED
|
@@ -4576,7 +4576,7 @@ function toDisplayName(name) {
|
|
|
4576
4576
|
// src/commands/scaffold.ts
|
|
4577
4577
|
var nameRegex = /^[a-z][a-z0-9-]*$/;
|
|
4578
4578
|
function render(template, data) {
|
|
4579
|
-
return template.replace(/\{\{\.Name\}\}/g, data.name).replace(/\{\{\.ID\}\}/g, data.id).replace(/\{\{\.DisplayName\}\}/g, data.displayName).replace(/\{\{\.DisplayNameNoSpace\}\}/g, data.displayNameNoSpace);
|
|
4579
|
+
return template.replace(/\{\{\.Name\}\}/g, data.name).replace(/\{\{\.ID\}\}/g, data.id).replace(/\{\{\.IDUpper\}\}/g, data.idUpper).replace(/\{\{\.DisplayName\}\}/g, data.displayName).replace(/\{\{\.DisplayNameNoSpace\}\}/g, data.displayNameNoSpace);
|
|
4580
4580
|
}
|
|
4581
4581
|
function writeTemplate(templateDir, tmplName, outPath, data) {
|
|
4582
4582
|
const tmplPath = join(templateDir, tmplName);
|
|
@@ -4609,6 +4609,7 @@ async function scaffold(nameArg, options) {
|
|
|
4609
4609
|
const data = {
|
|
4610
4610
|
name,
|
|
4611
4611
|
id,
|
|
4612
|
+
idUpper: id.toUpperCase().replace(/[^A-Z0-9]/g, "_"),
|
|
4612
4613
|
displayName,
|
|
4613
4614
|
displayNameNoSpace: displayName.replace(/ /g, "")
|
|
4614
4615
|
};
|
|
@@ -7591,8 +7592,9 @@ async function build(options) {
|
|
|
7591
7592
|
}
|
|
7592
7593
|
|
|
7593
7594
|
// src/commands/dev.ts
|
|
7594
|
-
import { existsSync as existsSync7, mkdirSync as mkdirSync3, writeFileSync as writeFileSync5, unlinkSync, cpSync } from "fs";
|
|
7595
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync3, writeFileSync as writeFileSync5, unlinkSync, readFileSync as readFileSync5, cpSync } from "fs";
|
|
7595
7596
|
import { join as join10 } from "path";
|
|
7597
|
+
import { createHash as createHash2 } from "crypto";
|
|
7596
7598
|
|
|
7597
7599
|
// node_modules/chokidar/esm/index.js
|
|
7598
7600
|
import { stat as statcb } from "fs";
|
|
@@ -9262,18 +9264,34 @@ async function dev() {
|
|
|
9262
9264
|
}
|
|
9263
9265
|
});
|
|
9264
9266
|
const distDir = join10(root, "dist");
|
|
9265
|
-
const
|
|
9267
|
+
const bundleFile = join10(distDir, `space-${m.id}.iife.js`);
|
|
9268
|
+
let lastChecksum = "";
|
|
9269
|
+
const distWatcher = watch(bundleFile, { ignoreInitial: false });
|
|
9266
9270
|
distWatcher.on("all", () => {
|
|
9267
|
-
if (existsSync7(
|
|
9268
|
-
|
|
9269
|
-
|
|
9270
|
-
|
|
9271
|
-
|
|
9272
|
-
|
|
9273
|
-
|
|
9274
|
-
|
|
9275
|
-
|
|
9276
|
-
|
|
9271
|
+
if (!existsSync7(bundleFile))
|
|
9272
|
+
return;
|
|
9273
|
+
const bundleData = readFileSync5(bundleFile);
|
|
9274
|
+
const checksum = createHash2("sha256").update(bundleData).digest("hex");
|
|
9275
|
+
if (checksum === lastChecksum)
|
|
9276
|
+
return;
|
|
9277
|
+
lastChecksum = checksum;
|
|
9278
|
+
const raw = readRaw(root);
|
|
9279
|
+
writeWithBuild(distDir, raw, {
|
|
9280
|
+
checksum,
|
|
9281
|
+
size: bundleData.length,
|
|
9282
|
+
hostApiVersion: "0.2.0",
|
|
9283
|
+
builtAt: new Date().toISOString()
|
|
9284
|
+
});
|
|
9285
|
+
mkdirSync3(installDir, { recursive: true });
|
|
9286
|
+
cpSync(distDir, installDir, { recursive: true });
|
|
9287
|
+
writeFileSync5(join10(installDir, ".dev"), "dev");
|
|
9288
|
+
const devInstall = devSpaceDir(m.id);
|
|
9289
|
+
const devParent = join10(devInstall, "..");
|
|
9290
|
+
if (existsSync7(devParent)) {
|
|
9291
|
+
mkdirSync3(devInstall, { recursive: true });
|
|
9292
|
+
cpSync(distDir, devInstall, { recursive: true });
|
|
9293
|
+
}
|
|
9294
|
+
console.log(source_default.green(`Installed → ${m.id}`));
|
|
9277
9295
|
});
|
|
9278
9296
|
console.log(source_default.green("Watching for changes... (Ctrl+C to stop)"));
|
|
9279
9297
|
await new Promise(() => {});
|
|
@@ -9306,7 +9324,7 @@ function run() {
|
|
|
9306
9324
|
}
|
|
9307
9325
|
|
|
9308
9326
|
// src/commands/publish.ts
|
|
9309
|
-
import { readFileSync as readFileSync7, writeFileSync as writeFileSync7, statSync as
|
|
9327
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync7, statSync as statSync6, unlinkSync as unlinkSync3 } from "fs";
|
|
9310
9328
|
import { join as join14, basename as basename6 } from "path";
|
|
9311
9329
|
|
|
9312
9330
|
// src/lib/auth.ts
|
|
@@ -9349,7 +9367,7 @@ function clear() {
|
|
|
9349
9367
|
}
|
|
9350
9368
|
|
|
9351
9369
|
// src/lib/pack.ts
|
|
9352
|
-
import { readdirSync as
|
|
9370
|
+
import { readdirSync as readdirSync3, statSync as statSync5, existsSync as existsSync10 } from "fs";
|
|
9353
9371
|
import { join as join13 } from "path";
|
|
9354
9372
|
import { tmpdir } from "os";
|
|
9355
9373
|
import { execSync as execSync3 } from "child_process";
|
|
@@ -9391,8 +9409,8 @@ async function packSource(root) {
|
|
|
9391
9409
|
if (existsSync10(join13(root, name)))
|
|
9392
9410
|
entries.push(name);
|
|
9393
9411
|
}
|
|
9394
|
-
for (const entry of
|
|
9395
|
-
if (
|
|
9412
|
+
for (const entry of readdirSync3(root)) {
|
|
9413
|
+
if (statSync5(join13(root, entry)).isDirectory())
|
|
9396
9414
|
continue;
|
|
9397
9415
|
if (allowedRootFiles.includes(entry))
|
|
9398
9416
|
continue;
|
|
@@ -9412,7 +9430,7 @@ async function packSource(root) {
|
|
|
9412
9430
|
const excludes = "--exclude=node_modules --exclude=dist --exclude=.git --exclude=*.env --exclude=*.log --exclude=*.lock --exclude=*.lockb";
|
|
9413
9431
|
const cmd = `tar czf "${tarballPath}" ${excludes} ${validEntries.join(" ")}`;
|
|
9414
9432
|
execSync3(cmd, { cwd: root });
|
|
9415
|
-
const size =
|
|
9433
|
+
const size = statSync5(tarballPath).size;
|
|
9416
9434
|
if (size > MAX_SIZE) {
|
|
9417
9435
|
throw new Error(`Source exceeds maximum size of ${MAX_SIZE / 1024 / 1024}MB`);
|
|
9418
9436
|
}
|
|
@@ -9552,7 +9570,7 @@ async function publish(options) {
|
|
|
9552
9570
|
let tarballPath;
|
|
9553
9571
|
try {
|
|
9554
9572
|
tarballPath = await packSource(root);
|
|
9555
|
-
const size =
|
|
9573
|
+
const size = statSync6(tarballPath).size;
|
|
9556
9574
|
spinner.succeed(`Source packed (${formatBytes(size)})`);
|
|
9557
9575
|
} catch (err) {
|
|
9558
9576
|
spinner.fail("Pack failed");
|
|
@@ -10084,7 +10102,7 @@ function updateBarrel(modelsDir, modelName) {
|
|
|
10084
10102
|
}
|
|
10085
10103
|
|
|
10086
10104
|
// src/commands/graph/push.ts
|
|
10087
|
-
import { existsSync as existsSync16, readdirSync as
|
|
10105
|
+
import { existsSync as existsSync16, readdirSync as readdirSync4, readFileSync as readFileSync12 } from "fs";
|
|
10088
10106
|
import { join as join20, basename as basename7 } from "path";
|
|
10089
10107
|
async function graphPush() {
|
|
10090
10108
|
const root = process.cwd();
|
|
@@ -10098,7 +10116,7 @@ async function graphPush() {
|
|
|
10098
10116
|
console.error(source_default.red("No src/models/ directory found. Run 'construct graph init' first."));
|
|
10099
10117
|
process.exit(1);
|
|
10100
10118
|
}
|
|
10101
|
-
const modelFiles =
|
|
10119
|
+
const modelFiles = readdirSync4(modelsDir).filter((f) => f.endsWith(".ts") && f !== "index.ts");
|
|
10102
10120
|
if (modelFiles.length === 0) {
|
|
10103
10121
|
console.error(source_default.red("No model files found in src/models/"));
|
|
10104
10122
|
console.log(source_default.dim(" Generate one: construct graph g User name:string email:string"));
|
|
@@ -10222,6 +10240,150 @@ function parseModelFile(content, fileName) {
|
|
|
10222
10240
|
return result;
|
|
10223
10241
|
}
|
|
10224
10242
|
|
|
10243
|
+
// src/commands/graph/migrate.ts
|
|
10244
|
+
import { existsSync as existsSync17, readdirSync as readdirSync5, readFileSync as readFileSync13 } from "fs";
|
|
10245
|
+
import { join as join21, basename as basename8 } from "path";
|
|
10246
|
+
async function graphMigrate(options) {
|
|
10247
|
+
const root = process.cwd();
|
|
10248
|
+
if (!exists(root)) {
|
|
10249
|
+
console.error(source_default.red("No space.manifest.json found in current directory"));
|
|
10250
|
+
process.exit(1);
|
|
10251
|
+
}
|
|
10252
|
+
const m = read(root);
|
|
10253
|
+
const modelsDir = join21(root, "src", "models");
|
|
10254
|
+
if (!existsSync17(modelsDir)) {
|
|
10255
|
+
console.error(source_default.red("No src/models/ directory. Run 'construct graph init' first."));
|
|
10256
|
+
process.exit(1);
|
|
10257
|
+
}
|
|
10258
|
+
let creds;
|
|
10259
|
+
try {
|
|
10260
|
+
creds = load2();
|
|
10261
|
+
} catch (err) {
|
|
10262
|
+
console.error(source_default.red(err.message));
|
|
10263
|
+
process.exit(1);
|
|
10264
|
+
}
|
|
10265
|
+
const graphURL = process.env.GRAPH_URL || "https://graph.construct.space";
|
|
10266
|
+
const spinner = ora("Fetching current schema...").start();
|
|
10267
|
+
let serverModels = [];
|
|
10268
|
+
try {
|
|
10269
|
+
const resp = await fetch(`${graphURL}/api/schemas/${m.id}`, {
|
|
10270
|
+
headers: { Authorization: `Bearer ${creds.token}` }
|
|
10271
|
+
});
|
|
10272
|
+
if (resp.ok) {
|
|
10273
|
+
const data = await resp.json();
|
|
10274
|
+
serverModels = data.models || [];
|
|
10275
|
+
}
|
|
10276
|
+
spinner.succeed("Schema fetched");
|
|
10277
|
+
} catch {
|
|
10278
|
+
spinner.fail("Could not fetch schema");
|
|
10279
|
+
process.exit(1);
|
|
10280
|
+
}
|
|
10281
|
+
const modelFiles = readdirSync5(modelsDir).filter((f) => f.endsWith(".ts") && f !== "index.ts");
|
|
10282
|
+
const localModels = [];
|
|
10283
|
+
for (const file of modelFiles) {
|
|
10284
|
+
const content = readFileSync13(join21(modelsDir, file), "utf-8");
|
|
10285
|
+
const model = parseModelFields(content, basename8(file, ".ts"));
|
|
10286
|
+
if (model)
|
|
10287
|
+
localModels.push(model);
|
|
10288
|
+
}
|
|
10289
|
+
console.log();
|
|
10290
|
+
let hasChanges = false;
|
|
10291
|
+
for (const server of serverModels) {
|
|
10292
|
+
const local = localModels.find((m2) => m2.name === server.name);
|
|
10293
|
+
const serverFields = (server.fields || []).map((f) => f.name);
|
|
10294
|
+
if (!local) {
|
|
10295
|
+
console.log(source_default.yellow(` Model "${server.name}" exists on server but not locally`));
|
|
10296
|
+
hasChanges = true;
|
|
10297
|
+
continue;
|
|
10298
|
+
}
|
|
10299
|
+
const localFields = local.fields.map((f) => f.name);
|
|
10300
|
+
for (const sf of serverFields) {
|
|
10301
|
+
if (!localFields.includes(sf)) {
|
|
10302
|
+
console.log(source_default.red(` - ${server.name}.${sf}`), source_default.dim("(on server, not in local model — can drop)"));
|
|
10303
|
+
hasChanges = true;
|
|
10304
|
+
}
|
|
10305
|
+
}
|
|
10306
|
+
for (const lf of localFields) {
|
|
10307
|
+
if (!serverFields.includes(lf)) {
|
|
10308
|
+
console.log(source_default.green(` + ${server.name}.${lf}`), source_default.dim("(new — will be added on push)"));
|
|
10309
|
+
hasChanges = true;
|
|
10310
|
+
}
|
|
10311
|
+
}
|
|
10312
|
+
}
|
|
10313
|
+
for (const local of localModels) {
|
|
10314
|
+
if (!serverModels.find((m2) => m2.name === local.name)) {
|
|
10315
|
+
console.log(source_default.green(` + Model "${local.name}"`), source_default.dim("(new — will be created on push)"));
|
|
10316
|
+
hasChanges = true;
|
|
10317
|
+
}
|
|
10318
|
+
}
|
|
10319
|
+
if (!hasChanges) {
|
|
10320
|
+
console.log(source_default.green(" Schema is in sync — no changes needed"));
|
|
10321
|
+
return;
|
|
10322
|
+
}
|
|
10323
|
+
console.log();
|
|
10324
|
+
if (!options?.apply) {
|
|
10325
|
+
console.log(source_default.dim(" Run with --apply to apply destructive changes"));
|
|
10326
|
+
console.log(source_default.dim(' Or run "construct graph push" to add new fields/models'));
|
|
10327
|
+
return;
|
|
10328
|
+
}
|
|
10329
|
+
const proceed = await dist_default4({ message: "Apply destructive schema changes? This cannot be undone." });
|
|
10330
|
+
if (!proceed) {
|
|
10331
|
+
console.log("Cancelled.");
|
|
10332
|
+
return;
|
|
10333
|
+
}
|
|
10334
|
+
const migrateSpinner = ora("Applying migrations...").start();
|
|
10335
|
+
try {
|
|
10336
|
+
const resp = await fetch(`${graphURL}/api/schemas/migrate`, {
|
|
10337
|
+
method: "POST",
|
|
10338
|
+
headers: {
|
|
10339
|
+
"Content-Type": "application/json",
|
|
10340
|
+
Authorization: `Bearer ${creds.token}`,
|
|
10341
|
+
"X-Space-ID": m.id
|
|
10342
|
+
},
|
|
10343
|
+
body: JSON.stringify({
|
|
10344
|
+
space_id: m.id,
|
|
10345
|
+
project_id: "default",
|
|
10346
|
+
local_models: localModels
|
|
10347
|
+
})
|
|
10348
|
+
});
|
|
10349
|
+
if (!resp.ok) {
|
|
10350
|
+
const body = await resp.text();
|
|
10351
|
+
migrateSpinner.fail("Migration failed");
|
|
10352
|
+
console.error(source_default.red(` ${resp.status}: ${body}`));
|
|
10353
|
+
process.exit(1);
|
|
10354
|
+
}
|
|
10355
|
+
const result = await resp.json();
|
|
10356
|
+
migrateSpinner.succeed("Migrations applied");
|
|
10357
|
+
if (result.dropped?.length) {
|
|
10358
|
+
for (const col of result.dropped) {
|
|
10359
|
+
console.log(source_default.red(` Dropped: ${col}`));
|
|
10360
|
+
}
|
|
10361
|
+
}
|
|
10362
|
+
if (result.altered?.length) {
|
|
10363
|
+
for (const col of result.altered) {
|
|
10364
|
+
console.log(source_default.yellow(` Altered: ${col}`));
|
|
10365
|
+
}
|
|
10366
|
+
}
|
|
10367
|
+
} catch (err) {
|
|
10368
|
+
migrateSpinner.fail("Migration failed");
|
|
10369
|
+
console.error(source_default.red(` ${err.message}`));
|
|
10370
|
+
process.exit(1);
|
|
10371
|
+
}
|
|
10372
|
+
}
|
|
10373
|
+
function parseModelFields(content, fileName) {
|
|
10374
|
+
const modelMatch = content.match(/defineModel\s*\(\s*['"](\w+)['"]/);
|
|
10375
|
+
if (!modelMatch)
|
|
10376
|
+
return null;
|
|
10377
|
+
const fields = [];
|
|
10378
|
+
const fieldRegex = /(\w+)\s*:\s*field\.(\w+)\(\s*(?:\[([^\]]*)\])?\s*\)((?:\.\w+\([^)]*\))*)/g;
|
|
10379
|
+
let match;
|
|
10380
|
+
while ((match = fieldRegex.exec(content)) !== null) {
|
|
10381
|
+
const [, name, type] = match;
|
|
10382
|
+
fields.push({ name, type });
|
|
10383
|
+
}
|
|
10384
|
+
return { name: modelMatch[1], fields };
|
|
10385
|
+
}
|
|
10386
|
+
|
|
10225
10387
|
// src/index.ts
|
|
10226
10388
|
var VERSION = "1.0.1";
|
|
10227
10389
|
var program2 = new Command;
|
|
@@ -10241,6 +10403,7 @@ var graph = program2.command("graph").description("Construct Graph — data mode
|
|
|
10241
10403
|
graph.command("init").description("Initialize Graph in a space project").action(() => graphInit());
|
|
10242
10404
|
graph.command("generate <model> [fields...]").alias("g").description("Generate a data model").option("--access <rules>", "Access rules (e.g. read:member,create:member,update:owner,delete:admin)").action((model, fields, opts) => generate2(model, fields, opts));
|
|
10243
10405
|
graph.command("push").description("Register models with the Graph service").action(async () => graphPush());
|
|
10406
|
+
graph.command("migrate").description("Compare local models with server schema and apply changes").option("--apply", "Apply destructive changes (drop columns, alter constraints)").action(async (opts) => graphMigrate(opts));
|
|
10244
10407
|
var space = program2.command("space").description("Space development commands");
|
|
10245
10408
|
space.command("scaffold [name]").alias("new").alias("create").option("--with-tests", "Include E2E testing boilerplate").action(async (name, opts) => scaffold(name, opts));
|
|
10246
10409
|
space.command("build").option("--entry-only").action(async (opts) => build(opts));
|
package/package.json
CHANGED