@quonfig/cli 0.0.15 → 0.0.17

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 (46) hide show
  1. package/README.md +85 -34
  2. package/dist/commands/init.d.ts +1 -0
  3. package/dist/commands/init.js +18 -0
  4. package/dist/commands/migrate.js +48 -2
  5. package/dist/commands/pull.d.ts +14 -1
  6. package/dist/commands/pull.js +64 -4
  7. package/dist/commands/push.d.ts +53 -0
  8. package/dist/commands/push.js +370 -0
  9. package/dist/commands/workspace/bootstrap.d.ts +7 -0
  10. package/dist/commands/workspace/bootstrap.js +58 -2
  11. package/dist/migrate/local-write.d.ts +8 -2
  12. package/dist/migrate/local-write.js +55 -7
  13. package/dist/migrate/migration-report.d.ts +18 -0
  14. package/dist/migrate/migration-report.js +64 -1
  15. package/dist/migrate/push-to-cloud.d.ts +14 -3
  16. package/dist/migrate/push-to-cloud.js +77 -6
  17. package/dist/migrate/source.d.ts +50 -1
  18. package/dist/migrate/sources/launch/translate.d.ts +27 -1
  19. package/dist/migrate/sources/launch/translate.js +123 -6
  20. package/dist/migrate/sources/launch/types.d.ts +3 -0
  21. package/dist/migrate/sources/launch/zod-to-json-schema.d.ts +9 -0
  22. package/dist/migrate/sources/launch/zod-to-json-schema.js +521 -0
  23. package/dist/migrate/sources/launch.js +52 -7
  24. package/dist/push/bare-path-diff.d.ts +40 -0
  25. package/dist/push/bare-path-diff.js +126 -0
  26. package/dist/push/confirm.d.ts +10 -0
  27. package/dist/push/confirm.js +108 -0
  28. package/dist/push/diff-summary.d.ts +56 -0
  29. package/dist/push/diff-summary.js +115 -0
  30. package/dist/push/identity-check.d.ts +53 -0
  31. package/dist/push/identity-check.js +135 -0
  32. package/dist/push/run-push.d.ts +107 -0
  33. package/dist/push/run-push.js +318 -0
  34. package/dist/util/get-client.js +5 -4
  35. package/dist/util/get-valid-token.d.ts +3 -1
  36. package/dist/util/get-valid-token.js +28 -3
  37. package/dist/util/gitea-api.d.ts +15 -1
  38. package/dist/util/gitea-api.js +1 -0
  39. package/dist/util/gitea-token-storage.d.ts +7 -0
  40. package/dist/util/quonfig-json.d.ts +44 -0
  41. package/dist/util/quonfig-json.js +65 -0
  42. package/dist/verify/validate.js +6 -2
  43. package/oclif.manifest.json +104 -1
  44. package/package.json +3 -3
  45. /package/dist/{migrate/push-strategy.d.ts → util/clone-and-stack-push.d.ts} +0 -0
  46. /package/dist/{migrate/push-strategy.js → util/clone-and-stack-push.js} +0 -0
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Reader/writer for the workspace's `quonfig.json` file.
3
+ *
4
+ * Today the file is `{ environments: string[] }`. We are adding an optional
5
+ * `workspace: "<slug>"` pin (Guard 1 in `project/plans/cli-git-sync.md`)
6
+ * so commands like `qfg push` can confirm that the local dir belongs to the
7
+ * cloud workspace they think it does.
8
+ *
9
+ * The reader is permissive: missing file or missing field returns `undefined`
10
+ * (no throw). It only throws when the file exists but cannot be parsed.
11
+ *
12
+ * The writer preserves all unknown fields, sticks to 2-space indent, and
13
+ * always ends with a trailing newline — matching how `qfg init` and the
14
+ * migrate flow create the file today.
15
+ */
16
+ import * as fs from 'node:fs';
17
+ import * as path from 'node:path';
18
+ const QUONFIG_JSON = 'quonfig.json';
19
+ async function readQuonfigJson(dir) {
20
+ const filePath = path.join(dir, QUONFIG_JSON);
21
+ let raw;
22
+ try {
23
+ raw = await fs.promises.readFile(filePath, 'utf8');
24
+ }
25
+ catch (error) {
26
+ if (error.code === 'ENOENT')
27
+ return undefined;
28
+ throw error;
29
+ }
30
+ // JSON.parse will throw on malformed JSON — that's the documented behavior.
31
+ const parsed = JSON.parse(raw);
32
+ if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
33
+ throw new TypeError(`${QUONFIG_JSON} must be a JSON object`);
34
+ }
35
+ return parsed;
36
+ }
37
+ /**
38
+ * Read the workspace slug pin from `<dir>/quonfig.json`.
39
+ *
40
+ * Returns `undefined` when the file is missing OR when the `workspace`
41
+ * field is absent OR when the field is present but not a string.
42
+ * Throws only when the file exists but contains malformed JSON, since
43
+ * that is a real bug a caller should surface.
44
+ */
45
+ export async function readWorkspaceSlug(dir) {
46
+ const parsed = await readQuonfigJson(dir);
47
+ if (!parsed)
48
+ return undefined;
49
+ const slug = parsed.workspace;
50
+ return typeof slug === 'string' ? slug : undefined;
51
+ }
52
+ /**
53
+ * Write the workspace slug pin into `<dir>/quonfig.json`.
54
+ *
55
+ * If the file exists, all other fields are preserved and only `workspace`
56
+ * is set/overwritten. If the file is missing, a minimal `{workspace: slug}`
57
+ * file is created. Format is 2-space indent + trailing newline to match
58
+ * the file shape produced by `qfg init` and `qfg migrate`.
59
+ */
60
+ export async function writeWorkspaceSlug(dir, slug) {
61
+ const existing = (await readQuonfigJson(dir)) ?? {};
62
+ const next = { ...existing, workspace: slug };
63
+ const filePath = path.join(dir, QUONFIG_JSON);
64
+ await fs.promises.writeFile(filePath, JSON.stringify(next, null, 2) + '\n', 'utf8');
65
+ }
@@ -279,7 +279,9 @@ export function validateWorkspace(workspaceDir) {
279
279
  quonfigParsed = null;
280
280
  issues.push({ file: 'quonfig.json', message: `Invalid JSON: ${error.message}`, severity: 'error' });
281
281
  }
282
- const quonfigResult = z.object({ environments: z.array(z.string()) }).safeParse(quonfigParsed);
282
+ const quonfigResult = z
283
+ .object({ environments: z.array(z.string()), workspace: z.string().optional() })
284
+ .safeParse(quonfigParsed);
283
285
  if (quonfigResult.success) {
284
286
  for (const envId of quonfigResult.data.environments) {
285
287
  declaredEnvIds.add(envId);
@@ -606,7 +608,9 @@ export function validateFileMap(files) {
606
608
  issues.push({ file: 'quonfig.json', message: `Invalid JSON: ${error.message}`, severity: 'error' });
607
609
  }
608
610
  if (quonfigParsed !== null) {
609
- const quonfigResult = z.object({ environments: z.array(z.string()) }).safeParse(quonfigParsed);
611
+ const quonfigResult = z
612
+ .object({ environments: z.array(z.string()), workspace: z.string().optional() })
613
+ .safeParse(quonfigParsed);
610
614
  if (quonfigResult.success) {
611
615
  for (const envId of quonfigResult.data.environments) {
612
616
  declaredEnvIds.add(envId);
@@ -567,6 +567,14 @@
567
567
  "name": "samples",
568
568
  "allowNo": true,
569
569
  "type": "boolean"
570
+ },
571
+ "workspace": {
572
+ "description": "Workspace slug to pin in quonfig.json (Guard 1). If omitted, no pin is written.",
573
+ "name": "workspace",
574
+ "required": false,
575
+ "hasDynamicHelp": false,
576
+ "multiple": false,
577
+ "type": "option"
570
578
  }
571
579
  },
572
580
  "hasDynamicHelp": false,
@@ -1287,6 +1295,101 @@
1287
1295
  "pull.js"
1288
1296
  ]
1289
1297
  },
1298
+ "push": {
1299
+ "aliases": [],
1300
+ "args": {},
1301
+ "description": "Push local config changes up to your workspace on Quonfig cloud.\n\nEnforces three guards before touching the remote:\n 1. The dir's quonfig.json \"workspace\" pin must match the backend (if set).\n 2. The dir's git origin must match the backend repo URL (if set).\n 3. A diff summary is shown; destructive changes (10+ deletes, >=25% of\n files, or an unpinned dir) require typing the workspace slug to confirm.\n\n qfg push # uses QUONFIG_DIR + profile workspace\n qfg push --dir ./our-config\n qfg push --dir ./our-config --workspace acme-prod\n qfg push --dir ./our-config --yes # skip normal Y/N (never skips typed-slug)\n qfg push --dir ./our-config --skip-validate",
1302
+ "examples": [
1303
+ "<%= config.bin %> push --dir ./our-config",
1304
+ "<%= config.bin %> push --workspace acme-prod --dir ./our-config",
1305
+ "<%= config.bin %> push --dir ./our-config --yes"
1306
+ ],
1307
+ "flags": {
1308
+ "json": {
1309
+ "description": "Format output as json.",
1310
+ "helpGroup": "GLOBAL",
1311
+ "name": "json",
1312
+ "allowNo": false,
1313
+ "type": "boolean"
1314
+ },
1315
+ "interactive": {
1316
+ "description": "Force interactive mode",
1317
+ "helpGroup": "GLOBAL",
1318
+ "name": "interactive",
1319
+ "allowNo": true,
1320
+ "type": "boolean"
1321
+ },
1322
+ "no-color": {
1323
+ "description": "Do not colorize output",
1324
+ "env": "NO_COLOR",
1325
+ "helpGroup": "GLOBAL",
1326
+ "name": "no-color",
1327
+ "allowNo": false,
1328
+ "type": "boolean"
1329
+ },
1330
+ "verbose": {
1331
+ "description": "Verbose output",
1332
+ "helpGroup": "GLOBAL",
1333
+ "name": "verbose",
1334
+ "allowNo": false,
1335
+ "type": "boolean"
1336
+ },
1337
+ "dir": {
1338
+ "description": "Local directory to push (defaults to QUONFIG_DIR env var)",
1339
+ "name": "dir",
1340
+ "hasDynamicHelp": false,
1341
+ "multiple": false,
1342
+ "type": "option"
1343
+ },
1344
+ "workspace": {
1345
+ "description": "Workspace slug or UUID (defaults to active profile)",
1346
+ "name": "workspace",
1347
+ "hasDynamicHelp": false,
1348
+ "multiple": false,
1349
+ "type": "option"
1350
+ },
1351
+ "yes": {
1352
+ "description": "Skip the standard Y/N confirm. Never skips typed-slug prompts.",
1353
+ "name": "yes",
1354
+ "allowNo": false,
1355
+ "type": "boolean"
1356
+ },
1357
+ "skip-validate": {
1358
+ "description": "Skip `qfg validate` preflight",
1359
+ "name": "skip-validate",
1360
+ "allowNo": false,
1361
+ "type": "boolean"
1362
+ },
1363
+ "no-pin-write": {
1364
+ "description": "Do not offer to write workspace slug into quonfig.json on success",
1365
+ "name": "no-pin-write",
1366
+ "allowNo": false,
1367
+ "type": "boolean"
1368
+ },
1369
+ "message": {
1370
+ "char": "m",
1371
+ "description": "Commit message for bare-path pushes (ignored on clone-path; push what is already committed)",
1372
+ "name": "message",
1373
+ "hasDynamicHelp": false,
1374
+ "multiple": false,
1375
+ "type": "option"
1376
+ }
1377
+ },
1378
+ "hasDynamicHelp": false,
1379
+ "hiddenAliases": [],
1380
+ "id": "push",
1381
+ "pluginAlias": "@quonfig/cli",
1382
+ "pluginName": "@quonfig/cli",
1383
+ "pluginType": "core",
1384
+ "strict": true,
1385
+ "enableJsonFlag": true,
1386
+ "isESM": true,
1387
+ "relativePath": [
1388
+ "dist",
1389
+ "commands",
1390
+ "push.js"
1391
+ ]
1392
+ },
1290
1393
  "schema": {
1291
1394
  "aliases": [],
1292
1395
  "args": {
@@ -2586,5 +2689,5 @@
2586
2689
  ]
2587
2690
  }
2588
2691
  },
2589
- "version": "0.0.15"
2692
+ "version": "0.0.17"
2590
2693
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "packageManager": "yarn@4.11.0+sha512.4e54aeace9141df2f0177c266b05ec50dc044638157dae128c471ba65994ac802122d7ab35bcd9e81641228b7dcf24867d28e750e0bcae8a05277d600008ad54",
3
3
  "name": "@quonfig/cli",
4
- "version": "0.0.15",
4
+ "version": "0.0.17",
5
5
  "bugs": {
6
6
  "url": "https://github.com/quonfig/cli/issues"
7
7
  },
@@ -32,6 +32,7 @@
32
32
  "lodash.camelcase": "^4.3.0",
33
33
  "mustache": "^4.2.0",
34
34
  "node-forge": "^1.4.0",
35
+ "typescript": "^5",
35
36
  "zod": "^4.1.12"
36
37
  },
37
38
  "description": "Quonfig CLI. For arbitrary targeting rules (email, plan, segment, custom property), run `qfg config-schema` then `qfg pull`.",
@@ -58,8 +59,7 @@
58
59
  "prettier": "^3.6.2",
59
60
  "shx": "^0.3.4",
60
61
  "ts-node": "^10.9.1",
61
- "tsx": "^4.21.0",
62
- "typescript": "^5"
62
+ "tsx": "^4.21.0"
63
63
  },
64
64
  "engines": {
65
65
  "node": ">=18.0.0"