@crombolo/cli 0.9.1 → 0.10.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.
Files changed (58) hide show
  1. package/bin/crombolo.ts +112 -37
  2. package/package.json +7 -7
  3. package/src/operations/commands.ts +203 -333
  4. package/src/templates.ts +1 -1
  5. package/templates/basic/bundle/package.json +16 -0
  6. package/templates/demo/harbourstone/bundle/package.json +16 -0
  7. package/templates/demo/harbourstone/files/schema/forms/contact.toml +2 -2
  8. package/templates/starter/bundle/package.json +16 -0
  9. package/src/operations/sections.ts +0 -423
  10. /package/templates/basic/bundle/{bundle.json → content/bundle.json} +0 -0
  11. /package/templates/basic/bundle/{nodes → content/nodes}/36955f6e-d110-469e-b280-72096eccd148.json +0 -0
  12. /package/templates/demo/harbourstone/bundle/{blobs → content/blobs}/180cc8ef/distillery-from-the-cliff-path.jpg +0 -0
  13. /package/templates/demo/harbourstone/bundle/{blobs → content/blobs}/68c9cfdf/bottle-brine-and-kiln.jpg +0 -0
  14. /package/templates/demo/harbourstone/bundle/{blobs → content/blobs}/6be45535/bottle-oloroso-pier.jpg +0 -0
  15. /package/templates/demo/harbourstone/bundle/{blobs → content/blobs}/6d8d6398/bottle-the-cove.jpg +0 -0
  16. /package/templates/demo/harbourstone/bundle/{blobs → content/blobs}/7b4713f8/copper-pot-stills.jpg +0 -0
  17. /package/templates/demo/harbourstone/bundle/{blobs → content/blobs}/c114b7d8/bottle-neap-tide.jpg +0 -0
  18. /package/templates/demo/harbourstone/bundle/{blobs → content/blobs}/fd337567/casks-in-the-sea-facing-warehouse.jpg +0 -0
  19. /package/templates/demo/harbourstone/bundle/{bundle.json → content/bundle.json} +0 -0
  20. /package/templates/demo/harbourstone/bundle/{nodes → content/nodes}/069cc3c5-c9f5-4792-90c9-e2bfcc4855b6.json +0 -0
  21. /package/templates/demo/harbourstone/bundle/{nodes → content/nodes}/13788fe7-1a45-4b48-8ae4-f4227e154e1e.json +0 -0
  22. /package/templates/demo/harbourstone/bundle/{nodes → content/nodes}/180cc8ef-caf1-4316-b6a7-e3b5aee41ffd.json +0 -0
  23. /package/templates/demo/harbourstone/bundle/{nodes → content/nodes}/22144db2-e214-4006-a0bb-53b5bd121fd6.json +0 -0
  24. /package/templates/demo/harbourstone/bundle/{nodes → content/nodes}/26e87610-e2d6-4b47-bf92-3967f7186907.json +0 -0
  25. /package/templates/demo/harbourstone/bundle/{nodes → content/nodes}/2caa099b-3884-472d-9cea-797a0f0c376f.json +0 -0
  26. /package/templates/demo/harbourstone/bundle/{nodes → content/nodes}/2efbdb5b-7888-4a49-bca5-c0f85b76e472.json +0 -0
  27. /package/templates/demo/harbourstone/bundle/{nodes → content/nodes}/2fe03891-1294-4344-8e24-47e8b212e5b0.json +0 -0
  28. /package/templates/demo/harbourstone/bundle/{nodes → content/nodes}/38b009f7-03c0-4fda-b5c7-843ad6044e9c.json +0 -0
  29. /package/templates/demo/harbourstone/bundle/{nodes → content/nodes}/3d312cc8-2937-40e1-ba14-fd781c9a862d.json +0 -0
  30. /package/templates/demo/harbourstone/bundle/{nodes → content/nodes}/3dc30fc1-973a-4217-b26f-24902210be17.json +0 -0
  31. /package/templates/demo/harbourstone/bundle/{nodes → content/nodes}/40107d59-f3e9-4592-aee8-5b03d4586cbd.json +0 -0
  32. /package/templates/demo/harbourstone/bundle/{nodes → content/nodes}/483a0d3b-a10c-4c9d-8c81-d9607d37e968.json +0 -0
  33. /package/templates/demo/harbourstone/bundle/{nodes → content/nodes}/4b760c0b-b83a-4eee-aa29-c225cfabf19b.json +0 -0
  34. /package/templates/demo/harbourstone/bundle/{nodes → content/nodes}/4e5c7f3f-54cb-4328-ad61-bc4c2736b890.json +0 -0
  35. /package/templates/demo/harbourstone/bundle/{nodes → content/nodes}/62f479ff-0875-453e-99ae-3bf1bec5e3c8.json +0 -0
  36. /package/templates/demo/harbourstone/bundle/{nodes → content/nodes}/68c9cfdf-6b40-47da-a1d2-17256a6cc6db.json +0 -0
  37. /package/templates/demo/harbourstone/bundle/{nodes → content/nodes}/6ad8f0f3-9334-47f1-b805-80c4b4b4fa88.json +0 -0
  38. /package/templates/demo/harbourstone/bundle/{nodes → content/nodes}/6be45535-83e5-4469-acf0-4127016721ae.json +0 -0
  39. /package/templates/demo/harbourstone/bundle/{nodes → content/nodes}/6d8d6398-738f-4f38-ad3c-e5d6e9a971fe.json +0 -0
  40. /package/templates/demo/harbourstone/bundle/{nodes → content/nodes}/7b4713f8-d917-457d-a9d8-7dd01a3617b4.json +0 -0
  41. /package/templates/demo/harbourstone/bundle/{nodes → content/nodes}/88ec1185-53b5-439a-a2ce-3f56603be4ff.json +0 -0
  42. /package/templates/demo/harbourstone/bundle/{nodes → content/nodes}/97469df2-107c-4010-8c14-4de88f735529.json +0 -0
  43. /package/templates/demo/harbourstone/bundle/{nodes → content/nodes}/a2eb439f-71d9-4ea6-89d7-c5bc72618281.json +0 -0
  44. /package/templates/demo/harbourstone/bundle/{nodes → content/nodes}/a562f0a2-cb6e-4ead-a1ce-4d180632419f.json +0 -0
  45. /package/templates/demo/harbourstone/bundle/{nodes → content/nodes}/a5e36a18-a2ef-4eb5-85b4-7e12b21cca06.json +0 -0
  46. /package/templates/demo/harbourstone/bundle/{nodes → content/nodes}/b1876d3a-50a7-4358-84bf-a0899932ffa9.json +0 -0
  47. /package/templates/demo/harbourstone/bundle/{nodes → content/nodes}/b4e38bbd-c1a3-431a-b7a9-5868aa32993a.json +0 -0
  48. /package/templates/demo/harbourstone/bundle/{nodes → content/nodes}/c114b7d8-0138-404b-9d8f-a39d992577a2.json +0 -0
  49. /package/templates/demo/harbourstone/bundle/{nodes → content/nodes}/cb4c569e-c28a-43b6-a4d7-d33bb544e973.json +0 -0
  50. /package/templates/demo/harbourstone/bundle/{nodes → content/nodes}/cd681b4b-9c4e-4624-9c13-450aa6a9c135.json +0 -0
  51. /package/templates/demo/harbourstone/bundle/{nodes → content/nodes}/d60ef12b-0f0a-4db5-9763-21fa52b8d4da.json +0 -0
  52. /package/templates/demo/harbourstone/bundle/{nodes → content/nodes}/d7f13222-d060-4496-b86d-bef21da963c1.json +0 -0
  53. /package/templates/demo/harbourstone/bundle/{nodes → content/nodes}/f36dd4a9-927c-4ca3-a790-c9c859ac7302.json +0 -0
  54. /package/templates/demo/harbourstone/bundle/{nodes → content/nodes}/fd337567-df67-41ca-ae94-9d84d8d6042f.json +0 -0
  55. /package/templates/starter/bundle/{bundle.json → content/bundle.json} +0 -0
  56. /package/templates/starter/bundle/{nodes → content/nodes}/634dd948-cf83-4cad-9b7b-7c938b33da76.json +0 -0
  57. /package/templates/starter/bundle/{nodes → content/nodes}/64d95359-d91a-4050-b440-3e5124a74999.json +0 -0
  58. /package/templates/starter/bundle/{nodes → content/nodes}/d2997ebb-36ca-47cb-bbc8-31fefca787c0.json +0 -0
package/bin/crombolo.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * crombolo start boot the site; resets and prints the admin password
7
7
  * crombolo start --keep-admin boot without touching the admin password
8
- * crombolo start --bundle <dir> apply a content bundle, once, then serve
8
+ * crombolo start --bundle <pkg> apply a bundle's content, once, then serve
9
9
  * crombolo init scaffold a site in the current directory
10
10
  * crombolo init --template <id> …from a starter template, content included
11
11
  * crombolo status what this environment is, without starting it
@@ -20,9 +20,9 @@
20
20
  * crombolo components check compile every component before a visitor does
21
21
  * crombolo assets list the stylesheets and scripts beside them
22
22
  *
23
- * crombolo content export write a subtree as a bundle, to move to another environment
24
- * crombolo content check read-only: what importing a bundle here would do
25
- * crombolo content import apply a bundle, in one transaction, and record the run
23
+ * crombolo content export write a subtree as a private bundle package, to move to another environment
24
+ * crombolo content check read-only: what installing a bundle's content here would do
25
+ * crombolo content import apply a bundle's content, in one transaction, and record the run
26
26
  * crombolo content runs the imports applied here
27
27
  * crombolo content revert put an import back, restoring what the site served
28
28
  *
@@ -35,13 +35,14 @@
35
35
  * crombolo upgrade --plan print the DDL each pending framework migration would run
36
36
  * crombolo upgrade back up, apply pending framework migrations, record them
37
37
  * crombolo upgrade ledger print migration_history
38
+ * crombolo bundle purge <id> back up, drop a bundle's tables and clear its state
38
39
  *
39
40
  * crombolo maintenance pause refuse editors' saves cluster-wide; readers unaffected
40
41
  * crombolo maintenance resume let editors save again
41
42
  */
42
43
  import { existsSync } from 'node:fs'
43
44
  import { mkdir, writeFile } from 'node:fs/promises'
44
- import { join, relative } from 'node:path'
45
+ import { basename, join, relative, resolve } from 'node:path'
45
46
  import { AuthStore, resetAdminPassword } from '@crombolo/auth'
46
47
  import { ObjectTypes } from '@crombolo/core'
47
48
  import {
@@ -112,6 +113,7 @@ import {
112
113
  syncDomainsFile,
113
114
  syncSchemaDirectory,
114
115
  undoDomainsFile,
116
+ VERSION,
115
117
  withoutNode,
116
118
  writeDictionaryUdt,
117
119
  writeDomains,
@@ -122,7 +124,7 @@ import {
122
124
  importBundle,
123
125
  isResolution,
124
126
  isRevertResolution,
125
- loadBundle,
127
+ packageNameFor,
126
128
  RESOLUTIONS,
127
129
  RESOLUTIONS_FILE,
128
130
  REVERT_RESOLUTIONS,
@@ -288,20 +290,24 @@ async function importStartupBundles(
288
290
  server: ServerHandle,
289
291
  config: CromboloConfig,
290
292
  ): Promise<string[]> {
291
- const dirs = flagValues('--bundle')
292
- if (dirs.length === 0) return []
293
+ const targets = flagValues('--bundle')
294
+ if (targets.length === 0) return []
293
295
 
294
296
  const lines: string[] = []
295
297
  const db = server.db
296
298
  let imported = false
297
- for (const dir of dirs) {
298
- const loaded = loadBundle(dir)
299
- for (const problem of loaded.problems) console.error(` ${problem.file}: ${problem.message}`)
300
- if (!loaded.set || loaded.problems.length > 0) {
301
- console.error(`\n${dir} cannot be read. The site has not started.`)
299
+ for (const target of targets) {
300
+ let resolved: ops.ResolvedBundle
301
+ try {
302
+ resolved = ops.readBundle(target, config.siteDir)
303
+ } catch (error) {
304
+ if (!(error instanceof ops.CommandError)) throw error
305
+ console.error(` ${error.message}`)
306
+ for (const problem of error.problems) console.error(` ${problem}`)
307
+ console.error(`\n${target} cannot be read. The site has not started.`)
302
308
  process.exit(1)
303
309
  }
304
- const set = loaded.set
310
+ const set = resolved.set
305
311
  const standing = await new TransferRunRepository(db).appliedFor(set.manifest.id)
306
312
  if (standing) {
307
313
  lines.push(` bundle ${set.manifest.id} already imported (run ${standing.id})`)
@@ -319,7 +325,7 @@ async function importStartupBundles(
319
325
  if (backup.path) console.log(` backup ${backup.path}`)
320
326
  }
321
327
 
322
- const prepared = await ops.transferOptions(db, config, set, transferInput(dir))
328
+ const prepared = await ops.transferOptions(db, config, resolved, transferInput(target))
323
329
  for (const problem of prepared.problems) console.error(` ${problem}`)
324
330
  const started = performance.now()
325
331
  const result = await importBundle(db, set, {
@@ -327,19 +333,22 @@ async function importStartupBundles(
327
333
  publish: flags.has('--publish'),
328
334
  nodeId: config.nodeId,
329
335
  label: flagValue('--label'),
336
+ package: { name: resolved.package.packageName, version: resolved.package.version },
330
337
  })
331
338
  await writeReport(db, result.check.findings, { source: 'transfer', scope: set.manifest.id })
332
339
  if (!result.runId) {
333
340
  printTransferFindings(result.check)
334
341
  console.error(
335
- `\n${dir} was refused: ${result.check.outstanding.length} finding(s) outstanding. ` +
342
+ `\n${target} was refused: ${result.check.outstanding.length} finding(s) outstanding. ` +
336
343
  'Nothing was written, and the site has not started.',
337
344
  )
338
345
  process.exit(1)
339
346
  }
340
347
  const c = result.check.counts
341
348
  const placed = describePlaced(
342
- loaded.blobs.size > 0 ? await placeBlobs(await mediaStoreFor(config), loaded.blobs) : null,
349
+ resolved.blobs.size > 0
350
+ ? await placeBlobs(await mediaStoreFor(config), resolved.blobs)
351
+ : null,
343
352
  )
344
353
  lines.push(
345
354
  ` bundle ${set.manifest.id}: ${c.create} created, ${c.update} updated` +
@@ -353,7 +362,7 @@ async function importStartupBundles(
353
362
  kind: 'transfer',
354
363
  durationMs: Math.round(performance.now() - started),
355
364
  appliedBy: config.nodeId,
356
- note: `run ${result.runId} (start --bundle)`,
365
+ note: `run ${result.runId}, ${resolved.package.packageName}@${resolved.package.version} (start --bundle)`,
357
366
  })
358
367
  imported = true
359
368
  }
@@ -992,14 +1001,14 @@ function describePlaced(placed: { placed: number; bytes: number } | null): strin
992
1001
  }
993
1002
 
994
1003
  /** The flags `check` and `import` share: resolutions, placement, blobs. */
995
- function transferInput(dir: string): ops.TransferInput {
1004
+ function transferInput(bundle: string): ops.TransferInput {
996
1005
  const allFlag = flagValue('--resolve-all')
997
1006
  if (allFlag !== undefined && !isResolution(allFlag)) {
998
1007
  console.error(` "${allFlag}" is not one of ${RESOLUTIONS.join(', ')}`)
999
1008
  process.exit(1)
1000
1009
  }
1001
1010
  return {
1002
- dir,
1011
+ bundle,
1003
1012
  under: flagValue('--under'),
1004
1013
  resolutions: parseResolutions(),
1005
1014
  resolveAll: allFlag as Resolution | undefined,
@@ -1058,7 +1067,9 @@ function describeBundle(bundle: ops.BundleSummary): void {
1058
1067
  async function contentImport(config: CromboloConfig): Promise<void> {
1059
1068
  const dir = positional[1]
1060
1069
  if (!dir) {
1061
- console.error('content import needs the bundle directory.')
1070
+ console.error(
1071
+ 'content import needs a bundle: a package directory, or the name of a bundle this site depends on.',
1072
+ )
1062
1073
  process.exit(1)
1063
1074
  }
1064
1075
  const result = await ops.contentImport(config, {
@@ -1285,7 +1296,9 @@ function printTransferFindings(check: TransferCheck): void {
1285
1296
  async function contentCheck(config: CromboloConfig): Promise<void> {
1286
1297
  const dir = positional[1]
1287
1298
  if (!dir) {
1288
- console.error('content check needs the bundle directory.')
1299
+ console.error(
1300
+ 'content check needs a bundle: a package directory, or the name of a bundle this site depends on.',
1301
+ )
1289
1302
  process.exit(1)
1290
1303
  }
1291
1304
  const result = await ops.contentCheck(config, transferInput(dir))
@@ -1296,7 +1309,7 @@ async function contentCheck(config: CromboloConfig): Promise<void> {
1296
1309
  printTransferFindings(result.check)
1297
1310
 
1298
1311
  if (flags.has('--save')) {
1299
- const target = join(dir, RESOLUTIONS_FILE)
1312
+ const target = join(result.payloadDir, RESOLUTIONS_FILE)
1300
1313
  await writeFile(
1301
1314
  target,
1302
1315
  writeResolutions({
@@ -1331,6 +1344,11 @@ async function contentExport(config: CromboloConfig): Promise<void> {
1331
1344
  drafts: flags.has('--drafts'),
1332
1345
  withBlobs: flags.has('--with-blobs'),
1333
1346
  withBlueprints: flags.has('--with-blueprints'),
1347
+ // An export is a private package; without --name it is named for its directory.
1348
+ package: {
1349
+ name: flagValue('--name') ?? packageNameFor(basename(resolve(out))),
1350
+ version: flagValue('--package-version'),
1351
+ },
1334
1352
  })
1335
1353
  for (const key of result.blobs.missing)
1336
1354
  console.error(` missing ${key} is not in this environment's media store`)
@@ -1341,6 +1359,7 @@ async function contentExport(config: CromboloConfig): Promise<void> {
1341
1359
  }
1342
1360
  const counts = Object.entries(result.counts)
1343
1361
  console.log(` wrote ${relative(cwd, out) || '.'}`)
1362
+ console.log(` package ${result.package.name}@${result.package.version} (private)`)
1344
1363
  console.log(
1345
1364
  ` bundle ${result.bundleId} (${result.snapshot}): ${
1346
1365
  counts.length > 0 ? counts.map(([kind, n]) => `${n} ${kind}`).join(', ') : 'nothing selected'
@@ -1362,7 +1381,7 @@ async function contentExport(config: CromboloConfig): Promise<void> {
1362
1381
  }
1363
1382
 
1364
1383
  /**
1365
- * `09-schema-as-code.md` leaves the dictionary in the database, because
1384
+ * `schema-as-code.md` leaves the dictionary in the database, because
1366
1385
  * translators work in production where `schema/` is read-only — so this is how
1367
1386
  * a lower environment gets the translations.
1368
1387
  */
@@ -1604,6 +1623,7 @@ async function importSite(): Promise<void> {
1604
1623
  siteName: flagValue('--name'),
1605
1624
  drafts: flags.has('--drafts'),
1606
1625
  staging: flagValue('--staging'),
1626
+ coreVersion: VERSION,
1607
1627
  })
1608
1628
  } catch (error) {
1609
1629
  console.error(` ${error instanceof Error ? error.message : error}`)
@@ -1827,7 +1847,7 @@ async function upgrade(): Promise<void> {
1827
1847
  try {
1828
1848
  for (const row of await readLedger(db))
1829
1849
  console.log(
1830
- ` ${row.appliedAt} ${row.kind.padEnd(8)} ${row.name} ${row.durationMs}ms${row.appliedBy ? ` by ${row.appliedBy}` : ''}${row.note ? ` (${row.note})` : ''}`,
1850
+ ` ${row.appliedAt} ${row.kind.padEnd(8)} ${row.bundle ? `[${row.bundle}] ` : ''}${row.name} ${row.durationMs}ms${row.appliedBy ? ` by ${row.appliedBy}` : ''}${row.note ? ` (${row.note})` : ''}`,
1831
1851
  )
1832
1852
  } finally {
1833
1853
  await db.close()
@@ -1837,6 +1857,11 @@ async function upgrade(): Promise<void> {
1837
1857
  case 'check': {
1838
1858
  if (!flags.has('--fix')) {
1839
1859
  const report = await ops.upgradeCheck(config, { set: parseSet() })
1860
+ for (const bundle of report.bundles)
1861
+ if (bundle.pending.length > 0)
1862
+ console.log(
1863
+ ` bundle ${bundle.id}: ${bundle.pending.map((step) => `${step.name} (${step.kind})`).join(', ')}`,
1864
+ )
1840
1865
  printFindings(report)
1841
1866
  if (!report.siteChecked)
1842
1867
  console.log(
@@ -1867,12 +1892,20 @@ async function upgrade(): Promise<void> {
1867
1892
  if (flags.has('--plan')) {
1868
1893
  const plan = await ops.upgradePlan(config)
1869
1894
  if (plan.steps.length === 0) console.log('No framework migration is pending.')
1870
- for (const step of plan.steps) {
1895
+ const printStep = (step: ops.PlannedStepResult, indent = '') => {
1871
1896
  console.log(
1872
- `\n== ${step.name} (${step.kind}${step.release ? `, release ${step.release}` : ''})`,
1897
+ `\n${indent}== ${step.name} (${step.kind}${step.release ? `, release ${step.release}` : ''})`,
1873
1898
  )
1874
- if (step.unplannable) console.log(` unplannable: ${step.unplannable}`)
1875
- for (const sql of step.statements) console.log(` ${sql};`)
1899
+ if (step.unplannable) console.log(`${indent} unplannable: ${step.unplannable}`)
1900
+ for (const sql of step.statements) console.log(`${indent} ${sql};`)
1901
+ }
1902
+ for (const step of plan.steps) printStep(step)
1903
+ // After core's, in the order `bundles` lists them, because that is the order they run.
1904
+ for (const bundle of plan.bundles) {
1905
+ console.log(
1906
+ `\nbundle ${bundle.id} (${bundle.name}): ${bundle.steps.length === 0 ? 'nothing pending' : `${bundle.steps.length} step(s)`}`,
1907
+ )
1908
+ for (const step of bundle.steps) printStep(step, ' ')
1876
1909
  }
1877
1910
  console.log(
1878
1911
  plan.site.checked
@@ -1900,6 +1933,8 @@ async function upgrade(): Promise<void> {
1900
1933
  if (result.action === 'nothing')
1901
1934
  return console.log('Nothing to upgrade: the database is at this version.')
1902
1935
  for (const name of result.frameworkApplied) console.log(` applied ${name}`)
1936
+ for (const bundle of result.bundlesApplied)
1937
+ for (const name of bundle.applied) console.log(` applied [${bundle.id}] ${name}`)
1903
1938
  console.log(
1904
1939
  ` schema ${result.schema?.action} ${result.schema?.version ?? ''}+${result.schema?.revision ?? ''}`,
1905
1940
  )
@@ -1910,6 +1945,37 @@ async function upgrade(): Promise<void> {
1910
1945
  }
1911
1946
  }
1912
1947
 
1948
+ /**
1949
+ * `bundle purge <id>`: drops a bundle's tables and forgets its state. Taking a
1950
+ * bundle out of `bundles` leaves them in place, so re-adding it resumes; this is
1951
+ * the deliberate act that does not.
1952
+ */
1953
+ async function bundlePurge(): Promise<void> {
1954
+ const config = await siteConfig()
1955
+ const id = positional[1]
1956
+ if (!id) {
1957
+ console.error('bundle purge needs the bundle id.')
1958
+ process.exit(1)
1959
+ }
1960
+ const plan = await ops.bundlePurgePlan(config, id)
1961
+ if (plan.tables.length === 0) console.log(` The bundle "${id}" has no tables in this database.`)
1962
+ else {
1963
+ console.log(` This drops ${plan.tables.length} table(s), and every row in them:`)
1964
+ for (const table of plan.tables) console.log(` ${table}`)
1965
+ }
1966
+ if (!flags.has('--yes')) {
1967
+ const answer = prompt(` Purge the bundle "${id}"? Type its id to confirm:`)
1968
+ if (answer?.trim() !== id) {
1969
+ console.log(' Nothing was dropped.')
1970
+ process.exit(1)
1971
+ }
1972
+ }
1973
+ const result = await ops.bundlePurge(config, id, { backupTaken: flags.has('--backup-taken') })
1974
+ if (result.backup.path) console.log(` backup ${result.backup.path}`)
1975
+ for (const table of result.dropped) console.log(` dropped ${table}`)
1976
+ console.log(` The bundle "${id}"'s state is cleared; adding it again starts its tables afresh.`)
1977
+ }
1978
+
1913
1979
  /**
1914
1980
  * `maintenance`: the cluster-wide pause on editing that a restore or a swap
1915
1981
  * needs. It is a database flag, so it reaches every node on its next write.
@@ -1948,7 +2014,7 @@ function help(): void {
1948
2014
  console.log(`crombolo <command>
1949
2015
 
1950
2016
  start [--keep-admin] boot the site in the current directory
1951
- [--bundle <dir>] apply a content bundle first, once per bundle, then serve;
2017
+ [--bundle <pkg>] apply a bundle's content first, once per bundle, then serve;
1952
2018
  takes the \`content import\` flags (--publish, --under, …)
1953
2019
  init [--force] [--name <site name>] [--postgres] [--template <id>]
1954
2020
  scaffold a site here; --template list prints the
@@ -1977,22 +2043,25 @@ function help(): void {
1977
2043
  assets new <stylesheet|script> <name>
1978
2044
  write an empty one in the place the backoffice edits
1979
2045
  content export --root <path|uuid> --out <dir>
1980
- write a content bundle: the subtree, its dependencies
1981
- and what the destination must already have
2046
+ write a private bundle package: the subtree, its
2047
+ dependencies and what the destination must already have
1982
2048
  [--only] [--drafts] [--with-blobs] [--with-blueprints]
1983
- content check <dir> what importing the bundle here would do, and what it
2049
+ [--name <npm name>] [--package-version <version>]
2050
+ A bundle is an npm package. <pkg> is its directory, or the name of one this
2051
+ site depends on.
2052
+ content check <pkg> what installing its content here would do, and what it
1984
2053
  needs from a person first; writes to the Changes dashboard
1985
2054
  [--under <path|uuid>] [--resolve <key>=take-bundle|keep-local|skip]
1986
2055
  [--resolve-all <choice>] [--allow-missing-blobs] [--save]
1987
- content import <dir> re-check, back up, then apply: documents and elements
2056
+ content import <pkg> re-check, back up, then apply: documents and elements
1988
2057
  arrive as drafts, media is live. One transaction, so a
1989
2058
  failure leaves nothing behind
1990
2059
  [--publish] [--label <text>] [--backup-taken] + the check flags
1991
2060
  [--no-schema] [--no-files] decline a section it carries
1992
2061
  [--replace-files] allow replacing files this site has;
1993
2062
  refused without it, and the originals are kept for a revert
1994
- bundle check <dir> the same as content check
1995
- bundle install <dir> the same as content import; a bundle carrying its own
2063
+ bundle check <pkg> the same as content check
2064
+ bundle install <pkg> the same as content import; a bundle carrying its own
1996
2065
  schema and views installs into a site that has neither
1997
2066
  content runs [--limit N] the imports applied here, newest first
1998
2067
  content publish <path|uuid> take it live; --descendants for the branch below it,
@@ -2031,6 +2100,8 @@ function help(): void {
2031
2100
  upgrade [--force <reason>] re-check, back up, apply framework steps, cut over, ledger
2032
2101
  upgrade --plan the DDL pending framework migrations would run
2033
2102
  upgrade ledger print migration_history
2103
+ bundle purge <id> [--yes] back up, then drop a bundle's tables and clear its state;
2104
+ refused while crombolo.config.ts still wires it
2034
2105
  maintenance pause [--reason <text>]
2035
2106
  refuse every editor's save, on every node, until resumed;
2036
2107
  readers are unaffected and no node drains
@@ -2073,11 +2144,15 @@ async function dispatch(): Promise<void> {
2073
2144
  break
2074
2145
  /**
2075
2146
  * The artifact's own name. A bundle that carries its own structure is what
2076
- * the Packages section builds (`docs/17-bundles.md`), and `install` reads
2147
+ * the Packages section builds (`docs/bundles.md`), and `install` reads
2077
2148
  * better than `import` for one — but it is the same operation on the same
2078
2149
  * artifact, so this is the same code path, not a second one.
2079
2150
  */
2080
2151
  case 'bundle':
2152
+ if (positional[0] === 'purge') {
2153
+ await bundlePurge()
2154
+ break
2155
+ }
2081
2156
  if (positional[0] === 'install') positional[0] = 'import'
2082
2157
  await content()
2083
2158
  break
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crombolo/cli",
3
- "version": "0.9.1",
3
+ "version": "0.10.0",
4
4
  "description": "The crombolo command line: scaffolding a site, schema, views, domains, content transfer and upgrades.",
5
5
  "license": "MIT",
6
6
  "author": "Crombolo",
@@ -34,11 +34,11 @@
34
34
  "access": "public"
35
35
  },
36
36
  "dependencies": {
37
- "@crombolo/auth": "0.9.1",
38
- "@crombolo/core": "0.9.1",
39
- "@crombolo/data": "0.9.1",
40
- "@crombolo/schema": "0.9.1",
41
- "@crombolo/server": "0.9.1",
42
- "@crombolo/transfer": "0.9.1"
37
+ "@crombolo/auth": "0.10.0",
38
+ "@crombolo/core": "0.10.0",
39
+ "@crombolo/data": "0.10.0",
40
+ "@crombolo/schema": "0.10.0",
41
+ "@crombolo/server": "0.10.0",
42
+ "@crombolo/transfer": "0.10.0"
43
43
  }
44
44
  }