@msn-control/liftoff 0.5.0 → 0.6.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.
@@ -0,0 +1,127 @@
1
+ import { manifestDisplayPath } from './file-system.js';
2
+ export const dependencyDefinitionLogicalNames = Object.freeze([
3
+ 'backend-pyproject',
4
+ 'function-worker-requirements',
5
+ 'node-backend-package',
6
+ 'node-backend-lock',
7
+ 'go-backend-module',
8
+ 'go-backend-checksums',
9
+ 'frontend-package',
10
+ 'frontend-lock',
11
+ 'power-apps-package',
12
+ 'power-apps-lock'
13
+ ]);
14
+ const dependencyDefinitionLogicalNameSet = new Set(dependencyDefinitionLogicalNames);
15
+ function comparePortable(left, right) {
16
+ return left < right ? -1 : left > right ? 1 : 0;
17
+ }
18
+ function displayEntry(entry) {
19
+ const current = manifestDisplayPath(entry.pathParts);
20
+ return entry.previousPathParts
21
+ ? `${manifestDisplayPath(entry.previousPathParts)} => ${current}`
22
+ : current;
23
+ }
24
+ function freezeSorted(values) {
25
+ return Object.freeze(values.sort(comparePortable));
26
+ }
27
+ function isActionable(entry) {
28
+ return entry.status !== 'orphan' &&
29
+ (entry.status !== 'unchanged' || entry.refreshHash === true);
30
+ }
31
+ export function buildUpdateImpact(entries) {
32
+ const creates = [];
33
+ const restores = [];
34
+ const replacements = [];
35
+ const moves = [];
36
+ const recordedStateRefreshes = [];
37
+ const conflicts = [];
38
+ const managedPathsRemoved = [];
39
+ const managedPathsRemovedOnOverwrite = [];
40
+ const orphansPreserved = [];
41
+ const dependencyDefinitions = [];
42
+ for (const entry of entries) {
43
+ switch (entry.status) {
44
+ case 'new':
45
+ creates.push(manifestDisplayPath(entry.pathParts));
46
+ break;
47
+ case 'missing':
48
+ restores.push(manifestDisplayPath(entry.pathParts));
49
+ break;
50
+ case 'upgrade':
51
+ replacements.push(manifestDisplayPath(entry.pathParts));
52
+ break;
53
+ case 'moved':
54
+ if (entry.cleanMove) {
55
+ moves.push(displayEntry(entry));
56
+ if (entry.previousPathParts) {
57
+ managedPathsRemoved.push(manifestDisplayPath(entry.previousPathParts));
58
+ }
59
+ }
60
+ else {
61
+ if (entry.sourceModified && entry.previousPathParts) {
62
+ conflicts.push(manifestDisplayPath(entry.previousPathParts));
63
+ }
64
+ if (entry.destinationOccupied && !entry.destinationMatches) {
65
+ conflicts.push(manifestDisplayPath(entry.pathParts));
66
+ }
67
+ if (entry.previousPathParts) {
68
+ managedPathsRemovedOnOverwrite.push(manifestDisplayPath(entry.previousPathParts));
69
+ }
70
+ }
71
+ break;
72
+ case 'conflict':
73
+ conflicts.push(manifestDisplayPath(entry.pathParts));
74
+ break;
75
+ case 'orphan':
76
+ orphansPreserved.push(manifestDisplayPath(entry.pathParts));
77
+ break;
78
+ case 'unchanged':
79
+ if (entry.refreshHash) {
80
+ recordedStateRefreshes.push(manifestDisplayPath(entry.pathParts));
81
+ }
82
+ break;
83
+ }
84
+ if (isActionable(entry) &&
85
+ dependencyDefinitionLogicalNameSet.has(entry.logicalName)) {
86
+ dependencyDefinitions.push(manifestDisplayPath(entry.pathParts));
87
+ }
88
+ }
89
+ const frozenCreates = freezeSorted(creates);
90
+ const frozenRestores = freezeSorted(restores);
91
+ const frozenReplacements = freezeSorted(replacements);
92
+ const frozenMoves = freezeSorted(moves);
93
+ const frozenRefreshes = freezeSorted(recordedStateRefreshes);
94
+ const frozenConflicts = freezeSorted(conflicts);
95
+ const frozenManagedPaths = freezeSorted(managedPathsRemoved);
96
+ const frozenManagedOverwritePaths = freezeSorted(managedPathsRemovedOnOverwrite);
97
+ const frozenOrphans = freezeSorted(orphansPreserved);
98
+ const frozenDependencyDefinitions = freezeSorted(dependencyDefinitions);
99
+ const safeActionCount = frozenCreates.length +
100
+ frozenRestores.length +
101
+ frozenReplacements.length +
102
+ frozenMoves.length +
103
+ frozenRefreshes.length;
104
+ const hasSafeActions = safeActionCount > 0;
105
+ const hasActionableChanges = hasSafeActions || frozenConflicts.length > 0;
106
+ return Object.freeze({
107
+ creates: frozenCreates,
108
+ restores: frozenRestores,
109
+ replacements: frozenReplacements,
110
+ moves: frozenMoves,
111
+ recordedStateRefreshes: frozenRefreshes,
112
+ conflicts: frozenConflicts,
113
+ managedPathsRemoved: frozenManagedPaths,
114
+ managedPathsRemovedOnOverwrite: frozenManagedOverwritePaths,
115
+ orphansPreserved: frozenOrphans,
116
+ dependencyDefinitions: frozenDependencyDefinitions,
117
+ safeActionCount,
118
+ localOrUserOwnedFilesAtRisk: frozenConflicts.length,
119
+ manifestWillUpdate: hasActionableChanges,
120
+ installsDependencies: false,
121
+ deletesOrphans: false,
122
+ retainsBackupAfterSuccess: false,
123
+ hasSafeActions,
124
+ hasActionableChanges
125
+ });
126
+ }
127
+ //# sourceMappingURL=update-impact.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"update-impact.js","sourceRoot":"","sources":["../src/update-impact.ts"],"names":[],"mappings":"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"}
@@ -24,7 +24,7 @@ plan -> init or migrate -> validate and doctor -> update -> dev and infra helper
24
24
  | `liftoff migrate <source>` | Creates a new sibling scaffold and filtered source copy without changing the source |
25
25
  | `liftoff validate [project]` | Validates manifest identity, durable files, workload metadata, and framework markers |
26
26
  | `liftoff doctor [project]` | Runs read-only workload-derived project and workstation diagnostics |
27
- | `liftoff update [project]` | Reports template and configuration drift without writing |
27
+ | `liftoff update [project]` | Reports drift and, in an interactive terminal, explains impact and asks before applying |
28
28
  | `liftoff update --apply` | Applies safe changes, preserves unforced conflicts, and records the resulting manifest |
29
29
  | `liftoff dev` | Prints workload-appropriate local development commands; it does not execute them |
30
30
  | `liftoff infra` | Prints OpenTofu guidance for API workloads and reports infrastructure as not applicable for Power Apps |
@@ -69,6 +69,22 @@ liftoff update --apply
69
69
  liftoff update --apply --force
70
70
  ```
71
71
 
72
+ With interactive input and output, plain `liftoff update` first reports drift
73
+ and its impact. Safe managed changes use a default-No confirmation. Local or
74
+ user-owned conflicts are listed separately and require another default-No
75
+ confirmation before replacement. Liftoff collects both decisions before any
76
+ preflight or write.
77
+
78
+ With redirected input or output, plain update remains a read-only check and
79
+ prints the explicit apply command. `--json` also remains prompt-free and
80
+ read-only unless combined with `--apply`. Explicit `--apply` and
81
+ `--apply --force` bypass prompts for automation.
82
+
83
+ The impact summary identifies affected dependency definitions, but update does
84
+ not install dependencies. Orphans are reported without deletion. Transaction
85
+ snapshots restore a failed update, but Liftoff retains no backup after a
86
+ successful overwrite; commit or copy local work first.
87
+
72
88
  `--force` is valid only with `--apply`. It overwrites reported file conflicts;
73
89
  it does not permit workload, API stack, GenAI pattern, framework, selected
74
90
  agent, or user-supplied Power Apps starter identity changes.
@@ -89,7 +105,7 @@ Exit codes:
89
105
 
90
106
  - `0`: success or a clean check.
91
107
  - `1`: invalid input, unsafe state, or command failure.
92
- - `2`: a read-only check found drift.
108
+ - `2`: a read-only check or declined interactive update found drift.
93
109
 
94
110
  Raw installer, framework, and dependency child stdout and stderr are forwarded
95
111
  unchanged.
@@ -67,8 +67,9 @@ Readers support schemas v2, v3, and v4:
67
67
  - V3 normalizes flat GenAI or API identity plus framework and agent metadata.
68
68
  - V4 represents the discriminated workload model, including Power Apps.
69
69
 
70
- A read-only `liftoff update` leaves an old manifest byte-for-byte unchanged. A
71
- successful apply writes v4 only after the file transaction succeeds. Skipped
70
+ A non-interactive or JSON check, or a declined interactive update, leaves an
71
+ old manifest byte-for-byte unchanged. A successful explicit or interactively
72
+ accepted apply writes v4 only after the file transaction succeeds. Skipped
72
73
  conflicts retain their recorded hashes.
73
74
 
74
75
  ## Artifact ownership
@@ -68,7 +68,11 @@ liftoff update
68
68
  liftoff update --apply
69
69
  ```
70
70
 
71
- Update is a read-only drift check unless `--apply` is present.
71
+ In an interactive terminal, plain update reports drift and impact, then asks
72
+ with a default of No before applying safe managed changes. Local or user-owned
73
+ conflicts require a separate default-No overwrite decision. Redirected and
74
+ JSON checks remain read-only and prompt-free; use `--apply` for explicit
75
+ automation consent.
72
76
 
73
77
  ## Existing non-Liftoff application
74
78
 
@@ -50,24 +50,39 @@ Individual project files use temporary-file replacement. Initialization keeps
50
50
  backups for replaced files and records created files and directories. A handled
51
51
  merge failure restores or removes those entries in reverse order.
52
52
 
53
- `liftoff update --apply` preflights all affected paths and applies generated
54
- file, move, delete, and manifest mutations as one rollback-capable
55
- transaction. Schema upgrades are committed only after the other mutations
56
- succeed. A corrected retry converges from the restored state.
53
+ An accepted interactive update or `liftoff update --apply` preflights all
54
+ affected paths and applies generated file, move, delete, and manifest mutations
55
+ as one rollback-capable transaction. Schema upgrades are committed only after
56
+ the other mutations succeed. A corrected retry converges from the restored
57
+ state.
57
58
 
58
59
  If automatic rollback itself cannot safely restore a path because another
59
60
  process changed it, Liftoff reports the incomplete rollback rather than
60
61
  overwriting unknown bytes.
61
62
 
63
+ Update snapshots exist only for rollback after a failed transaction. Liftoff
64
+ does not retain them as backups after success.
65
+
62
66
  ## Update ownership
63
67
 
64
- `liftoff update` is read-only by default:
68
+ Plain `liftoff update` always reports drift before any write:
65
69
 
66
70
  - Clean generated files remain unchanged.
67
- - New, missing, untouched-upgrade, and clean-move states can be applied.
71
+ - In an interactive terminal, new, missing, untouched-upgrade, clean-move, and
72
+ recorded-state changes are summarized and offered through a default-No
73
+ confirmation.
74
+ - With redirected input or output, or with `--json`, update remains a
75
+ prompt-free read-only check unless `--apply` is explicitly supplied.
68
76
  - Developer edits that also differ from the current template are conflicts.
69
- - Conflicts are skipped unless `--apply --force` is explicitly supplied.
77
+ - Safe-update consent does not authorize conflicts. Interactive conflicts are
78
+ listed by portable relative path and require a separate default-No overwrite
79
+ confirmation; automation uses `--apply --force`.
70
80
  - Orphans are reported and left on disk for manual review.
81
+ - Dependency definitions may be updated, but update never installs
82
+ dependencies.
83
+
84
+ All interactive decisions are collected before preflight or mutation.
85
+ Declining the safe update changes nothing and leaves drift exit code 2.
71
86
 
72
87
  Power Apps reconciliation reads only the packaged immutable starter. It does
73
88
  not fetch the upstream repository. Workload kind and user-edited starter
@@ -67,14 +67,21 @@ hand-edited unsafe path.
67
67
 
68
68
  ## Update reports conflicts or orphans
69
69
 
70
- `liftoff update` does not write. Review the report:
71
-
72
- - Apply safe changes with `liftoff update --apply`.
73
- - Developer conflicts remain untouched unless you explicitly use
74
- `liftoff update --apply --force`.
70
+ In an interactive terminal, `liftoff update` shows an impact summary and asks
71
+ before applying safe managed changes. The default answer is No.
72
+
73
+ - Local or user-owned conflicts are listed separately and require their own
74
+ default-No overwrite confirmation.
75
+ - With redirected input or output, review the report and apply safe changes
76
+ with `liftoff update --apply`.
77
+ - For prompt-free conflict replacement, use `liftoff update --apply --force`
78
+ only after reviewing every listed path.
75
79
  - Orphans are never deleted automatically.
80
+ - Update reports dependency-definition impact but does not install
81
+ dependencies.
76
82
 
77
- Commit local work before applying when possible.
83
+ Commit or copy local work before overwriting. Transaction rollback protects a
84
+ failed update, but Liftoff keeps no backup after success.
78
85
 
79
86
  ## Power Apps dependencies or CLI are missing
80
87
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@msn-control/liftoff",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Interactive CLI for governed GenAI, API, and Power Apps code app projects.",
5
5
  "type": "module",
6
6
  "license": "GPL-3.0-only",
@@ -44,8 +44,10 @@
44
44
  "scripts": {
45
45
  "build": "tsc -p tsconfig.json",
46
46
  "test": "vitest run",
47
+ "audit:template-dependencies": "node scripts/audit-template-dependencies.mjs",
47
48
  "refresh:power-apps-starter": "node scripts/refresh-power-apps-starter.mjs",
48
49
  "smoke:package": "npm run build && node scripts/package-smoke-test.mjs",
50
+ "verify:standard-node-templates": "npm run build && node scripts/verify-standard-node-templates.mjs",
49
51
  "verify:power-apps-starter": "npm run build && node scripts/verify-power-apps-starter.mjs",
50
52
  "verify:release-identity": "npm run build && node scripts/verify-release-identity.mjs",
51
53
  "verify:published": "npm run build && node scripts/verify-published-package.mjs",