@msn-control/liftoff 0.3.0 → 0.3.2
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 +674 -0
- package/README.md +48 -14
- package/dist/args.d.ts +19 -0
- package/dist/args.js +216 -39
- package/dist/args.js.map +1 -1
- package/dist/cli.js +12 -7
- package/dist/cli.js.map +1 -1
- package/dist/commands.js +43 -8
- package/dist/commands.js.map +1 -1
- package/dist/file-system.d.ts +3 -0
- package/dist/file-system.js +288 -40
- package/dist/file-system.js.map +1 -1
- package/dist/planner.js +113 -3
- package/dist/planner.js.map +1 -1
- package/dist/reconcile.d.ts +1 -0
- package/dist/reconcile.js +58 -12
- package/dist/reconcile.js.map +1 -1
- package/dist/standard-templates.js +147 -2
- package/dist/standard-templates.js.map +1 -1
- package/dist/templates.d.ts +18 -0
- package/dist/templates.js +924 -73
- package/dist/templates.js.map +1 -1
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
Status: implemented.
|
|
4
4
|
|
|
5
|
-
Liftoff is the Mission Control Launchpad Engine
|
|
5
|
+
Liftoff is the open-source Mission Control Launchpad Engine. It exposes the `liftoff` command and scaffolds governed GenAI applications or standard APIs with Docker Compose, OpenTofu, and spec-driven governance assets.
|
|
6
|
+
|
|
7
|
+
Source, issues, and contribution guidance live at [github.com/voyager163/liftoff](https://github.com/voyager163/liftoff).
|
|
6
8
|
|
|
7
9
|
## Installation
|
|
8
10
|
|
|
@@ -87,6 +89,39 @@ liftoff regions search korea --cloud azure
|
|
|
87
89
|
|
|
88
90
|
Azure is the available V1 provider. AWS and GCP are listed as planned provider adapters and are rejected before generation.
|
|
89
91
|
|
|
92
|
+
## Strict Commands And Safe Recovery
|
|
93
|
+
|
|
94
|
+
Liftoff validates each command before running it. Unknown flags or subcommands, missing flag values, invalid booleans, incompatible duplicates, and extra positional arguments exit 1 without generating files or printing a fallback helper command. Use command-specific help to see the accepted syntax:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
liftoff create --help
|
|
98
|
+
liftoff update --help
|
|
99
|
+
liftoff regions --help
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
`liftoff update --apply` preflights every path and destination before its first mutation. A new or moved artifact is adopted when the destination already contains the rendered bytes; different pre-existing bytes are reported as a conflict and skipped. `--force` overwrites reviewed conflicts. Orphans are reported but never deleted automatically. Any write, replacement, cleanup, or manifest failure exits 1 without a success summary or false manifest state, so fixing the filesystem issue and retrying is safe.
|
|
103
|
+
|
|
104
|
+
Manifest paths must be portable path-part arrays confined to the project. Traversal, absolute, drive-qualified, UNC, embedded-separator, empty, and symlink-escaping paths are rejected before artifact access. If validation reports an unsafe or malformed manifest, restore `liftoff.manifest.json` from version control or regenerate a fresh project with the matching Liftoff version. Do not repair the issue by weakening path validation or by retaining a hand-edited unsafe path.
|
|
105
|
+
|
|
106
|
+
## Generated Integration Configuration
|
|
107
|
+
|
|
108
|
+
GenAI starters contain executable, offline-testable integration boundaries:
|
|
109
|
+
|
|
110
|
+
- `PYDANTIC_AI_MODEL` selects the production PydanticAI model. Invoking an unconfigured production agent raises a clear configuration error rather than returning a successful placeholder.
|
|
111
|
+
- Redis Streams publishing uses `REDIS_URL` and `REDIS_STREAM_NAME`.
|
|
112
|
+
- Azure Service Bus publishing uses `SERVICE_BUS_QUEUE_NAME` and either `SERVICE_BUS_CONNECTION_STRING` or `SERVICE_BUS_FULLY_QUALIFIED_NAMESPACE`; `AZURE_CLIENT_ID` selects a user-assigned managed identity.
|
|
113
|
+
- Langfuse tracing requires both `LANGFUSE_PUBLIC_KEY` and `LANGFUSE_SECRET_KEY`, with optional `LANGFUSE_HOST`. Without both keys, tracing is explicitly disabled and reports no remote trace ID.
|
|
114
|
+
- Generated frontends read `VITE_API_BASE_URL` from `frontend/.env`, call the route selected by the project pattern or API stack, and expose loading, response, and failure states.
|
|
115
|
+
- Generated backends allow the local frontend origin by default. Set the comma-separated `CORS_ALLOWED_ORIGINS` value whenever `VITE_API_BASE_URL` points at a frontend on another origin; generated Azure infrastructure sets it to the deployed frontend URL.
|
|
116
|
+
|
|
117
|
+
Generated backend, messaging, tracing, and orchestration tests require no external model, Redis, Service Bus, or Langfuse service. Each generated README includes the fresh-project install, build, and test commands for its selected stack.
|
|
118
|
+
|
|
119
|
+
## Azure Deployment Contracts
|
|
120
|
+
|
|
121
|
+
Environment tfvars contain a deterministic 12-character lowercase alphanumeric `resource_suffix` for globally scoped Azure names. If Azure reports a collision, replace that environment's suffix with another unique value matching `^[a-z0-9]{12}$`; `tofu validate` rejects invalid overrides.
|
|
122
|
+
|
|
123
|
+
Worker-enabled projects configure `ServiceBusConnection__fullyQualifiedNamespace` and `ServiceBusConnection__clientId` for the attached user-assigned identity, and grant that identity's principal the Service Bus Data Receiver role. `function_worker_queue_name` drives the provisioned queue, Function setting, and output. Function host storage uses one complete key-backed `AzureWebJobsStorage` configuration rather than mixed partial identity settings.
|
|
124
|
+
|
|
90
125
|
## Generated Project Structure
|
|
91
126
|
|
|
92
127
|
Generated paths below are logical project structure examples. The CLI writes them using platform-correct filesystem handling on macOS, Linux, and Windows, and machine-readable manifests store path parts rather than joined path strings.
|
|
@@ -141,7 +176,7 @@ Generated projects contain two root files with different ownership models:
|
|
|
141
176
|
- `liftoff.config.json` is user-owned desired state after creation. Liftoff writes it once during generation and does not machine-rewrite it afterwards. Supported changes, such as adding an environment or enabling frontend output, are reconciled by `liftoff update`. Project type, API stack, and GenAI pattern changes are migrations and should use `liftoff migrate`.
|
|
142
177
|
- `liftoff.manifest.json` is the CLI-owned compatibility record. Manifest schema v2 records `artifactVersion`, the generating `liftoffVersion`, project type, API stack, applicable GenAI pattern, durable generated artifact `logicalName`s, categories, OS-neutral path parts, and `sha256:` content hashes.
|
|
143
178
|
|
|
144
|
-
The manifest lets `liftoff validate`, `liftoff doctor`, and `liftoff update` distinguish clean generated files from local edits. Seed content, such as the initial OpenSpec bootstrap change, is written once and intentionally omitted from the durable manifest so it can follow its own lifecycle.
|
|
179
|
+
The manifest lets `liftoff validate`, `liftoff doctor`, and `liftoff update` distinguish clean generated files from local edits. Seed content, such as the initial OpenSpec bootstrap change, is written once and intentionally omitted from the durable manifest so it can follow its own lifecycle. Treat the manifest as CLI-owned: restore or regenerate it when validation fails rather than hand-editing artifact paths or hashes.
|
|
145
180
|
|
|
146
181
|
## Contract Conventions
|
|
147
182
|
|
|
@@ -157,28 +192,27 @@ Generated projects contain persistent files that outlive any CLI release. The fo
|
|
|
157
192
|
|
|
158
193
|
## Development
|
|
159
194
|
|
|
160
|
-
|
|
195
|
+
Clone the public repository and install its development dependencies:
|
|
161
196
|
|
|
162
197
|
```bash
|
|
163
|
-
npm
|
|
164
|
-
npm test
|
|
165
|
-
npm run check
|
|
198
|
+
npm ci
|
|
166
199
|
```
|
|
167
200
|
|
|
168
|
-
|
|
201
|
+
Run contributor commands from the repository root:
|
|
169
202
|
|
|
170
203
|
```bash
|
|
171
|
-
npm run check
|
|
204
|
+
npm run check
|
|
205
|
+
npm run build
|
|
206
|
+
npm test
|
|
207
|
+
npm run smoke:package
|
|
172
208
|
```
|
|
173
209
|
|
|
174
|
-
|
|
210
|
+
The complete test suite exercises generated Python, Node.js, and Go projects. Install Python 3.12, Go 1.23, and OpenTofu 1.12 when working on generated-stack or infrastructure behavior.
|
|
175
211
|
|
|
176
|
-
|
|
177
|
-
npm run smoke:package --workspace @msn-control/liftoff
|
|
178
|
-
```
|
|
212
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for the contribution workflow and [SECURITY.md](SECURITY.md) for private vulnerability reporting.
|
|
179
213
|
|
|
180
214
|
## Release
|
|
181
215
|
|
|
182
|
-
The `Release Liftoff` workflow builds, tests, packs, smoke-installs, and publishes
|
|
216
|
+
The `Release Liftoff` workflow builds, tests, packs, smoke-installs, and publishes `@msn-control/liftoff` from this repository. Stable versions publish with the `latest` npm dist-tag; prerelease versions publish with `next`.
|
|
183
217
|
|
|
184
|
-
|
|
218
|
+
Releases use npm trusted publishing with provenance from this public repository. Publishing remains blocked unless npm authorizes `.github/workflows/release.yml` for `voyager163/liftoff`.
|
package/dist/args.d.ts
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
import type { ParsedArgs } from './types.js';
|
|
2
|
+
type FlagKind = 'boolean' | 'value';
|
|
3
|
+
interface FlagDefinition {
|
|
4
|
+
kind: FlagKind;
|
|
5
|
+
negatable?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface CommandDefinition {
|
|
8
|
+
description: string;
|
|
9
|
+
usage: string;
|
|
10
|
+
flags: Readonly<Record<string, FlagDefinition>>;
|
|
11
|
+
subcommands?: readonly string[];
|
|
12
|
+
defaultMaxPositionals: number;
|
|
13
|
+
subcommandMaxPositionals?: Readonly<Record<string, number>>;
|
|
14
|
+
}
|
|
15
|
+
export declare const commandDefinitions: Readonly<Record<string, CommandDefinition>>;
|
|
16
|
+
export declare class UsageError extends Error {
|
|
17
|
+
constructor(message: string);
|
|
18
|
+
}
|
|
2
19
|
export declare function parseArgs(argv: string[]): ParsedArgs;
|
|
20
|
+
export declare function formatCommandHelp(command: string): string;
|
|
3
21
|
export declare function readStringFlag(flags: ParsedArgs['flags'], name: string): string | undefined;
|
|
4
22
|
export declare function readBooleanFlag(flags: ParsedArgs['flags'], name: string): boolean | undefined;
|
|
5
23
|
export declare function readListFlag(flags: ParsedArgs['flags'], name: string): string[] | undefined;
|
|
24
|
+
export {};
|
package/dist/args.js
CHANGED
|
@@ -1,65 +1,242 @@
|
|
|
1
|
-
const
|
|
2
|
-
const
|
|
3
|
-
const
|
|
1
|
+
const booleanFlag = (negatable = false) => ({ kind: 'boolean', negatable });
|
|
2
|
+
const valueFlag = () => ({ kind: 'value' });
|
|
3
|
+
const helpFlag = { help: booleanFlag() };
|
|
4
|
+
const projectFlags = {
|
|
5
|
+
project: valueFlag(),
|
|
6
|
+
genai: booleanFlag(true),
|
|
7
|
+
api: valueFlag(),
|
|
8
|
+
pattern: valueFlag(),
|
|
9
|
+
cloud: valueFlag(),
|
|
10
|
+
region: valueFlag(),
|
|
11
|
+
frontend: booleanFlag(true),
|
|
12
|
+
environments: valueFlag(),
|
|
13
|
+
spec: valueFlag(),
|
|
14
|
+
config: valueFlag()
|
|
15
|
+
};
|
|
16
|
+
export const commandDefinitions = {
|
|
17
|
+
help: {
|
|
18
|
+
description: 'Show general or command-specific help',
|
|
19
|
+
usage: '[command]',
|
|
20
|
+
flags: helpFlag,
|
|
21
|
+
defaultMaxPositionals: 1
|
|
22
|
+
},
|
|
23
|
+
create: {
|
|
24
|
+
description: 'Generate a new project',
|
|
25
|
+
usage: '[project-name]',
|
|
26
|
+
flags: { ...projectFlags, yes: booleanFlag(), ...helpFlag },
|
|
27
|
+
defaultMaxPositionals: 1
|
|
28
|
+
},
|
|
29
|
+
plan: {
|
|
30
|
+
description: 'Preview generated artifacts',
|
|
31
|
+
usage: '',
|
|
32
|
+
flags: { ...projectFlags, ...helpFlag },
|
|
33
|
+
defaultMaxPositionals: 0
|
|
34
|
+
},
|
|
35
|
+
patterns: {
|
|
36
|
+
description: 'List GenAI patterns',
|
|
37
|
+
usage: '',
|
|
38
|
+
flags: helpFlag,
|
|
39
|
+
defaultMaxPositionals: 0
|
|
40
|
+
},
|
|
41
|
+
providers: {
|
|
42
|
+
description: 'List cloud providers',
|
|
43
|
+
usage: '',
|
|
44
|
+
flags: helpFlag,
|
|
45
|
+
defaultMaxPositionals: 0
|
|
46
|
+
},
|
|
47
|
+
regions: {
|
|
48
|
+
description: 'List or search provider regions',
|
|
49
|
+
usage: '[search <query>]',
|
|
50
|
+
flags: { cloud: valueFlag(), region: valueFlag(), ...helpFlag },
|
|
51
|
+
subcommands: ['search'],
|
|
52
|
+
defaultMaxPositionals: 0,
|
|
53
|
+
subcommandMaxPositionals: { search: 1 }
|
|
54
|
+
},
|
|
55
|
+
validate: {
|
|
56
|
+
description: 'Validate a generated project manifest',
|
|
57
|
+
usage: '[project-path]',
|
|
58
|
+
flags: { project: valueFlag(), ...helpFlag },
|
|
59
|
+
defaultMaxPositionals: 1
|
|
60
|
+
},
|
|
61
|
+
update: {
|
|
62
|
+
description: 'Reconcile a project with current templates',
|
|
63
|
+
usage: '[project-path]',
|
|
64
|
+
flags: {
|
|
65
|
+
project: valueFlag(),
|
|
66
|
+
apply: booleanFlag(),
|
|
67
|
+
force: booleanFlag(),
|
|
68
|
+
json: booleanFlag(),
|
|
69
|
+
...helpFlag
|
|
70
|
+
},
|
|
71
|
+
defaultMaxPositionals: 1
|
|
72
|
+
},
|
|
73
|
+
migrate: {
|
|
74
|
+
description: 'Adopt an existing project',
|
|
75
|
+
usage: '<source-path>',
|
|
76
|
+
flags: { ...projectFlags, yes: booleanFlag(), ...helpFlag },
|
|
77
|
+
defaultMaxPositionals: 1
|
|
78
|
+
},
|
|
79
|
+
doctor: {
|
|
80
|
+
description: 'Check local and project readiness',
|
|
81
|
+
usage: '',
|
|
82
|
+
flags: { cloud: valueFlag(), json: booleanFlag(), ...helpFlag },
|
|
83
|
+
defaultMaxPositionals: 0
|
|
84
|
+
},
|
|
85
|
+
dev: {
|
|
86
|
+
description: 'Print Docker Compose helper commands',
|
|
87
|
+
usage: '[up|down|logs|reset]',
|
|
88
|
+
flags: { profile: valueFlag(), ...helpFlag },
|
|
89
|
+
subcommands: ['up', 'down', 'logs', 'reset'],
|
|
90
|
+
defaultMaxPositionals: 0,
|
|
91
|
+
subcommandMaxPositionals: { up: 0, down: 0, logs: 0, reset: 0 }
|
|
92
|
+
},
|
|
93
|
+
infra: {
|
|
94
|
+
description: 'Print OpenTofu helper commands',
|
|
95
|
+
usage: '[init|plan|apply|output]',
|
|
96
|
+
flags: { env: valueFlag(), ...helpFlag },
|
|
97
|
+
subcommands: ['init', 'plan', 'apply', 'output'],
|
|
98
|
+
defaultMaxPositionals: 0,
|
|
99
|
+
subcommandMaxPositionals: { init: 0, plan: 0, apply: 0, output: 0 }
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
export class UsageError extends Error {
|
|
103
|
+
constructor(message) {
|
|
104
|
+
super(message);
|
|
105
|
+
this.name = 'UsageError';
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function assignFlag(flags, name, value) {
|
|
109
|
+
if (Object.hasOwn(flags, name)) {
|
|
110
|
+
throw new UsageError(`Flag --${name} may be provided only once.`);
|
|
111
|
+
}
|
|
112
|
+
flags[name] = value;
|
|
113
|
+
}
|
|
114
|
+
function parseBooleanValue(name, value) {
|
|
115
|
+
if (value === 'true') {
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
if (value === 'false') {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
throw new UsageError(`Flag --${name} expects true or false.`);
|
|
122
|
+
}
|
|
4
123
|
export function parseArgs(argv) {
|
|
5
|
-
|
|
124
|
+
if (argv.length === 0) {
|
|
125
|
+
return { positional: [], flags: {} };
|
|
126
|
+
}
|
|
127
|
+
if (argv[0] === '--help') {
|
|
128
|
+
return { command: 'help', positional: [], flags: {} };
|
|
129
|
+
}
|
|
130
|
+
const command = argv[0];
|
|
131
|
+
if (command.startsWith('-')) {
|
|
132
|
+
throw new UsageError(`Unknown option: ${command}. Run \`liftoff help\` for usage.`);
|
|
133
|
+
}
|
|
134
|
+
const definition = commandDefinitions[command];
|
|
135
|
+
if (!definition) {
|
|
136
|
+
throw new UsageError(`Unknown command: ${command}. Run \`liftoff help\` for usage.`);
|
|
137
|
+
}
|
|
138
|
+
const tokens = argv.slice(1);
|
|
139
|
+
let subcommand;
|
|
140
|
+
if (definition.subcommands && tokens[0] && !tokens[0].startsWith('-')) {
|
|
141
|
+
const candidate = tokens.shift();
|
|
142
|
+
if (!definition.subcommands.includes(candidate)) {
|
|
143
|
+
throw new UsageError(`Unsupported ${command} subcommand: ${candidate}. Use one of: ${definition.subcommands.join(', ')}.`);
|
|
144
|
+
}
|
|
145
|
+
subcommand = candidate;
|
|
146
|
+
}
|
|
6
147
|
const positional = [];
|
|
7
148
|
const flags = {};
|
|
8
|
-
|
|
9
|
-
const tokens = hasSubcommand ? rest : argv.slice(1);
|
|
10
|
-
const subcommand = hasSubcommand ? maybeSubcommand : undefined;
|
|
149
|
+
let positionalOnly = false;
|
|
11
150
|
for (let index = 0; index < tokens.length; index += 1) {
|
|
12
151
|
const token = tokens[index];
|
|
13
|
-
if (
|
|
14
|
-
|
|
152
|
+
if (token === '--') {
|
|
153
|
+
positionalOnly = true;
|
|
15
154
|
continue;
|
|
16
155
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
flags[withoutPrefix.slice(3)] = false;
|
|
156
|
+
if (positionalOnly || !token.startsWith('-')) {
|
|
157
|
+
positional.push(token);
|
|
20
158
|
continue;
|
|
21
159
|
}
|
|
160
|
+
if (!token.startsWith('--')) {
|
|
161
|
+
throw new UsageError(`Unknown option: ${token}. Liftoff options use --long-name syntax.`);
|
|
162
|
+
}
|
|
163
|
+
const withoutPrefix = token.slice(2);
|
|
22
164
|
const equalsIndex = withoutPrefix.indexOf('=');
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
165
|
+
const rawName = equalsIndex >= 0 ? withoutPrefix.slice(0, equalsIndex) : withoutPrefix;
|
|
166
|
+
const inlineValue = equalsIndex >= 0 ? withoutPrefix.slice(equalsIndex + 1) : undefined;
|
|
167
|
+
const negated = rawName.startsWith('no-');
|
|
168
|
+
const name = negated ? rawName.slice(3) : rawName;
|
|
169
|
+
const flagDefinition = definition.flags[name];
|
|
170
|
+
if (!flagDefinition) {
|
|
171
|
+
throw new UsageError(`Unknown flag for ${command}: --${rawName}.`);
|
|
172
|
+
}
|
|
173
|
+
if (negated) {
|
|
174
|
+
if (inlineValue !== undefined || flagDefinition.kind !== 'boolean' || !flagDefinition.negatable) {
|
|
175
|
+
throw new UsageError(`Flag --${name} does not support the --no-${name} form.`);
|
|
176
|
+
}
|
|
177
|
+
assignFlag(flags, name, false);
|
|
26
178
|
continue;
|
|
27
179
|
}
|
|
28
|
-
if (
|
|
29
|
-
flags
|
|
180
|
+
if (flagDefinition.kind === 'boolean') {
|
|
181
|
+
assignFlag(flags, name, inlineValue === undefined ? true : parseBooleanValue(name, inlineValue));
|
|
30
182
|
continue;
|
|
31
183
|
}
|
|
32
|
-
if (
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
throw new Error(`Missing value for --${withoutPrefix}.`);
|
|
184
|
+
if (inlineValue !== undefined) {
|
|
185
|
+
if (inlineValue.length === 0) {
|
|
186
|
+
throw new UsageError(`Missing value for --${name}.`);
|
|
36
187
|
}
|
|
37
|
-
flags
|
|
38
|
-
index += 1;
|
|
188
|
+
assignFlag(flags, name, inlineValue);
|
|
39
189
|
continue;
|
|
40
190
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
191
|
+
const next = tokens[index + 1];
|
|
192
|
+
if (!next || next.startsWith('-')) {
|
|
193
|
+
throw new UsageError(`Missing value for --${name}.`);
|
|
194
|
+
}
|
|
195
|
+
assignFlag(flags, name, next);
|
|
196
|
+
index += 1;
|
|
197
|
+
}
|
|
198
|
+
const maxPositionals = subcommand
|
|
199
|
+
? definition.subcommandMaxPositionals?.[subcommand] ?? 0
|
|
200
|
+
: definition.defaultMaxPositionals;
|
|
201
|
+
if (positional.length > maxPositionals) {
|
|
202
|
+
throw new UsageError(`Too many positional arguments for ${command}${subcommand ? ` ${subcommand}` : ''}. ` +
|
|
203
|
+
`Usage: liftoff ${command}${definition.usage ? ` ${definition.usage}` : ''}`);
|
|
204
|
+
}
|
|
205
|
+
if (command === 'help' && positional[0] && !commandDefinitions[positional[0]]) {
|
|
206
|
+
throw new UsageError(`Unknown command for help: ${positional[0]}.`);
|
|
207
|
+
}
|
|
208
|
+
return { command, subcommand, positional, flags };
|
|
209
|
+
}
|
|
210
|
+
export function formatCommandHelp(command) {
|
|
211
|
+
const definition = commandDefinitions[command];
|
|
212
|
+
if (!definition) {
|
|
213
|
+
throw new UsageError(`Unknown command for help: ${command}.`);
|
|
214
|
+
}
|
|
215
|
+
const lines = [
|
|
216
|
+
`${command} - ${definition.description}`,
|
|
217
|
+
'',
|
|
218
|
+
`Usage: liftoff ${command}${definition.usage ? ` ${definition.usage}` : ''}`
|
|
219
|
+
];
|
|
220
|
+
if (definition.subcommands) {
|
|
221
|
+
lines.push('', `Subcommands: ${definition.subcommands.join(', ')}`);
|
|
222
|
+
}
|
|
223
|
+
const flagNames = Object.entries(definition.flags).map(([name, flag]) => {
|
|
224
|
+
const value = flag.kind === 'value' ? ' <value>' : '';
|
|
225
|
+
const negated = flag.negatable ? ` / --no-${name}` : '';
|
|
226
|
+
return ` --${name}${value}${negated}`;
|
|
227
|
+
});
|
|
228
|
+
if (flagNames.length > 0) {
|
|
229
|
+
lines.push('', 'Options:', ...flagNames);
|
|
230
|
+
}
|
|
231
|
+
return `${lines.join('\n')}\n`;
|
|
49
232
|
}
|
|
50
233
|
export function readStringFlag(flags, name) {
|
|
51
234
|
const value = flags[name];
|
|
52
|
-
|
|
53
|
-
return value;
|
|
54
|
-
}
|
|
55
|
-
return undefined;
|
|
235
|
+
return typeof value === 'string' ? value : undefined;
|
|
56
236
|
}
|
|
57
237
|
export function readBooleanFlag(flags, name) {
|
|
58
238
|
const value = flags[name];
|
|
59
|
-
|
|
60
|
-
return value;
|
|
61
|
-
}
|
|
62
|
-
return undefined;
|
|
239
|
+
return typeof value === 'boolean' ? value : undefined;
|
|
63
240
|
}
|
|
64
241
|
export function readListFlag(flags, name) {
|
|
65
242
|
const value = flags[name];
|
package/dist/args.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"args.js","sourceRoot":"","sources":["../src/args.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"args.js","sourceRoot":"","sources":["../src/args.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.js
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { parseArgs } from './args.js';
|
|
3
3
|
import { runCommand } from './commands.js';
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
4
|
+
try {
|
|
5
|
+
const parsed = parseArgs(process.argv.slice(2));
|
|
6
|
+
process.exitCode = await runCommand(parsed, {
|
|
7
|
+
cwd: process.cwd(),
|
|
8
|
+
stdout: process.stdout,
|
|
9
|
+
stderr: process.stderr
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
14
|
+
process.exitCode = 1;
|
|
15
|
+
}
|
|
11
16
|
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAE3C,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAE3C,IAAI,CAAC;IACH,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAChD,OAAO,CAAC,QAAQ,GAAG,MAAM,UAAU,CAAC,MAAM,EAAE;QAC1C,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE;QAClB,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,MAAM,EAAE,OAAO,CAAC,MAAM;KACvB,CAAC,CAAC;AACL,CAAC;AAAC,OAAO,KAAK,EAAE,CAAC;IACf,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACpF,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACvB,CAAC"}
|
package/dist/commands.js
CHANGED
|
@@ -3,9 +3,9 @@ import { existsSync } from 'node:fs';
|
|
|
3
3
|
import { cp, mkdir, mkdtemp, rm, stat, writeFile } from 'node:fs/promises';
|
|
4
4
|
import os from 'node:os';
|
|
5
5
|
import path from 'node:path';
|
|
6
|
-
import { readBooleanFlag, readListFlag, readStringFlag } from './args.js';
|
|
6
|
+
import { formatCommandHelp, readBooleanFlag, readListFlag, readStringFlag } from './args.js';
|
|
7
7
|
import { apiStacks, listRegions, patterns, providers, searchRegions } from './catalogs.js';
|
|
8
|
-
import { artifactPath, assertNewOrEmptyDirectory, deleteProjectFile, findProjectRoot, loadManifest, manifestDisplayPath, resolveTargetRoot, validateGeneratedProject, writeArtifacts, writeProjectFile } from './file-system.js';
|
|
8
|
+
import { artifactPath, assertNewOrEmptyDirectory, deleteProjectFile, findProjectRoot, loadManifest, manifestDisplayPath, resolveProjectPath, resolveTargetRoot, validateGeneratedProject, writeArtifacts, writeProjectFile } from './file-system.js';
|
|
9
9
|
import { confirmPlan, promptForCreateOptions } from './interactive.js';
|
|
10
10
|
import { renderMigrationChecklist, renderMigrationProposal, renderMigrationTasks, seedMigrationGroups } from './migrate-plan.js';
|
|
11
11
|
import { buildProjectPlan, formatProjectPlan, loadConfigOptions, mergeOptions, PlanValidationError } from './planner.js';
|
|
@@ -16,11 +16,20 @@ import { buildArtifacts, buildManifest } from './templates.js';
|
|
|
16
16
|
import { liftoffVersion } from './version.js';
|
|
17
17
|
export async function runCommand(parsed, context) {
|
|
18
18
|
try {
|
|
19
|
+
if (parsed.command && readBooleanFlag(parsed.flags, 'help')) {
|
|
20
|
+
context.stdout.write(formatCommandHelp(parsed.command));
|
|
21
|
+
return 0;
|
|
22
|
+
}
|
|
19
23
|
switch (parsed.command) {
|
|
20
24
|
case undefined:
|
|
21
25
|
case 'help':
|
|
22
26
|
case '--help':
|
|
23
|
-
|
|
27
|
+
if (parsed.positional[0]) {
|
|
28
|
+
context.stdout.write(formatCommandHelp(parsed.positional[0]));
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
printHelp(context.stdout);
|
|
32
|
+
}
|
|
24
33
|
return 0;
|
|
25
34
|
case 'create':
|
|
26
35
|
return await createCommand(parsed, context);
|
|
@@ -281,6 +290,23 @@ function isDirtyGitWorktree(projectRoot) {
|
|
|
281
290
|
const result = spawnSync('git', ['status', '--porcelain'], { cwd: projectRoot, encoding: 'utf8' });
|
|
282
291
|
return result.status === 0 && result.stdout.trim().length > 0;
|
|
283
292
|
}
|
|
293
|
+
async function preflightUpdate(projectRoot, entries, force) {
|
|
294
|
+
for (const entry of entries) {
|
|
295
|
+
const writesDestination = entry.status === 'new' ||
|
|
296
|
+
entry.status === 'missing' ||
|
|
297
|
+
entry.status === 'upgrade' ||
|
|
298
|
+
entry.status === 'moved' && (entry.cleanMove === true || force) ||
|
|
299
|
+
entry.status === 'conflict' && force;
|
|
300
|
+
if (writesDestination) {
|
|
301
|
+
await resolveProjectPath(projectRoot, entry.pathParts);
|
|
302
|
+
}
|
|
303
|
+
if (entry.previousPathParts &&
|
|
304
|
+
(entry.status === 'moved' && (entry.cleanMove === true || force) || entry.status === 'conflict' && force)) {
|
|
305
|
+
await resolveProjectPath(projectRoot, entry.previousPathParts);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
await resolveProjectPath(projectRoot, ['liftoff.manifest.json']);
|
|
309
|
+
}
|
|
284
310
|
async function updateCommand(parsed, context) {
|
|
285
311
|
const apply = readBooleanFlag(parsed.flags, 'apply') ?? false;
|
|
286
312
|
const force = readBooleanFlag(parsed.flags, 'force') ?? false;
|
|
@@ -353,6 +379,7 @@ async function updateCommand(parsed, context) {
|
|
|
353
379
|
if (isDirtyGitWorktree(projectRoot)) {
|
|
354
380
|
context.stdout.write('Hint: the project worktree has uncommitted changes - consider committing before applying.\n');
|
|
355
381
|
}
|
|
382
|
+
await preflightUpdate(projectRoot, entries, force);
|
|
356
383
|
const written = [];
|
|
357
384
|
const skipped = [];
|
|
358
385
|
for (const entry of entries) {
|
|
@@ -365,7 +392,9 @@ async function updateCommand(parsed, context) {
|
|
|
365
392
|
break;
|
|
366
393
|
case 'moved':
|
|
367
394
|
if (entry.cleanMove || force) {
|
|
368
|
-
|
|
395
|
+
if (!entry.destinationMatches) {
|
|
396
|
+
await writeProjectFile(projectRoot, entry.pathParts, entry.rendered.content);
|
|
397
|
+
}
|
|
369
398
|
await deleteProjectFile(projectRoot, entry.previousPathParts);
|
|
370
399
|
written.push(entry);
|
|
371
400
|
}
|
|
@@ -376,6 +405,9 @@ async function updateCommand(parsed, context) {
|
|
|
376
405
|
case 'conflict':
|
|
377
406
|
if (force) {
|
|
378
407
|
await writeProjectFile(projectRoot, entry.pathParts, entry.rendered.content);
|
|
408
|
+
if (entry.previousPathParts) {
|
|
409
|
+
await deleteProjectFile(projectRoot, entry.previousPathParts);
|
|
410
|
+
}
|
|
379
411
|
written.push(entry);
|
|
380
412
|
}
|
|
381
413
|
else {
|
|
@@ -389,16 +421,19 @@ async function updateCommand(parsed, context) {
|
|
|
389
421
|
const oldByName = new Map(manifest.artifacts.map((artifact) => [artifact.logicalName, artifact]));
|
|
390
422
|
const skippedByName = new Map(skipped.map((entry) => [entry.logicalName, entry]));
|
|
391
423
|
const nextManifest = buildManifest(plan, render.filter((artifact) => artifact.logicalName !== 'manifest'));
|
|
392
|
-
nextManifest.artifacts = nextManifest.artifacts.
|
|
424
|
+
nextManifest.artifacts = nextManifest.artifacts.flatMap((artifact) => {
|
|
393
425
|
// config is user-owned after create: carry the recorded entry forward untouched
|
|
394
426
|
if (artifact.logicalName === 'liftoff-config') {
|
|
395
|
-
return oldByName.get('liftoff-config') ?? artifact;
|
|
427
|
+
return [oldByName.get('liftoff-config') ?? artifact];
|
|
396
428
|
}
|
|
397
429
|
if (!skippedByName.has(artifact.logicalName)) {
|
|
398
|
-
return artifact;
|
|
430
|
+
return [artifact];
|
|
399
431
|
}
|
|
400
432
|
const previous = oldByName.get(artifact.logicalName);
|
|
401
|
-
|
|
433
|
+
if (!previous) {
|
|
434
|
+
return [];
|
|
435
|
+
}
|
|
436
|
+
return [{ ...artifact, pathParts: previous.pathParts, contentHash: previous.contentHash }];
|
|
402
437
|
});
|
|
403
438
|
for (const entry of entries) {
|
|
404
439
|
if (entry.status === 'orphan') {
|