@m-kopa/launchpad-cli 0.60.1 → 0.61.1
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/CHANGELOG.md +20 -0
- package/README.md +10 -0
- package/dist/cli.js +218 -186
- package/dist/commands/deploy-flags.d.ts.map +1 -1
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/init.d.ts +2 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/deploy/served-verify.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
- package/skills/launchpad-content-pr/SKILL.md +13 -1
- package/skills/launchpad-deploy/SKILL.md +28 -23
- package/skills/launchpad-deploy-status/SKILL.md +1 -1
- package/skills/launchpad-destroy/SKILL.md +1 -1
- package/skills/launchpad-identity/SKILL.md +1 -1
- package/skills/launchpad-onboard/SKILL.md +1 -1
- package/skills/launchpad-report/SKILL.md +1 -1
- package/skills/launchpad-status/SKILL.md +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,26 @@ pre-1.0 minor bumps may carry breaking changes per ADR 0005.
|
|
|
8
8
|
|
|
9
9
|
## Unreleased
|
|
10
10
|
|
|
11
|
+
## 0.61.1 - 2026-09-17
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- Post-deploy checks stop at redirects instead of comparing an SSO sign-in page with the app.
|
|
16
|
+
Private apps report that content verification needs an authenticated browser, while direct content mismatches still fail.
|
|
17
|
+
|
|
18
|
+
## 0.61.0 - 2026-09-17
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- New apps start private to their verified creator and platform administrators, and are unlisted by default.
|
|
23
|
+
- Company-wide grants require explicit human confirmation before publication.
|
|
24
|
+
Sensitive apps also require administrator approval.
|
|
25
|
+
- Deployment skills explain the audience and hand confirmation to the builder.
|
|
26
|
+
Existing audiences and unchanged content deployments retain their behaviour.
|
|
27
|
+
- The platform minimum supported CLI version is 0.60.1 for deployments and other supported mutating commands.
|
|
28
|
+
Clients reporting stable versions below 0.60.1 receive an upgrade-required error directing users to `launchpad update`.
|
|
29
|
+
Existing apps and read-only commands remain available.
|
|
30
|
+
|
|
11
31
|
## 0.60.1 - 2026-09-17
|
|
12
32
|
|
|
13
33
|
### Changed
|
package/README.md
CHANGED
|
@@ -107,3 +107,13 @@ than a hand-maintained second copy.
|
|
|
107
107
|
Pre-1.0 consumer-pinning policy (per ADR 0005): consumers must pin
|
|
108
108
|
exact versions until v1.0.0. Caret ranges are forbidden. Each
|
|
109
109
|
minor-version bump may carry breaking changes.
|
|
110
|
+
|
|
111
|
+
## Default app access
|
|
112
|
+
|
|
113
|
+
New apps are private to their verified creator and platform administrators, and are unlisted by default.
|
|
114
|
+
Omit `--group` when running `launchpad init` to keep that audience.
|
|
115
|
+
Selecting groups grants access to members of any selected group.
|
|
116
|
+
A new company-wide grant requires the builder's explicit browser confirmation before publishing; sensitive apps also require platform-admin approval.
|
|
117
|
+
Agents must never submit that confirmation for the human.
|
|
118
|
+
Catalogue listing changes discovery, not access.
|
|
119
|
+
See [App access](../../docs/app-access.md) for the manifest and deployment contract.
|
package/dist/cli.js
CHANGED
|
@@ -19,7 +19,7 @@ var __toESM = (mod, isNodeMode, target) => {
|
|
|
19
19
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
20
20
|
|
|
21
21
|
// src/version.ts
|
|
22
|
-
var CLI_VERSION = "0.
|
|
22
|
+
var CLI_VERSION = "0.61.1";
|
|
23
23
|
|
|
24
24
|
// src/config.ts
|
|
25
25
|
import { readFileSync } from "node:fs";
|
|
@@ -2570,11 +2570,23 @@ import { join as join6 } from "node:path";
|
|
|
2570
2570
|
import { parse as parseYaml2 } from "yaml";
|
|
2571
2571
|
|
|
2572
2572
|
// ../launchpad-engine/dist/schema.js
|
|
2573
|
+
import { z as z2 } from "zod";
|
|
2574
|
+
|
|
2575
|
+
// ../launchpad-engine/dist/audience.js
|
|
2573
2576
|
import { z } from "zod";
|
|
2577
|
+
var PrivateOwnerSchema = z.object({ oid: z.uuid(), email: z.email() }).strict();
|
|
2578
|
+
function validateAudience(access, ctx) {
|
|
2579
|
+
const groups = access.allowed_entra_groups ?? (access.allowed_entra_group ? [access.allowed_entra_group] : []);
|
|
2580
|
+
const valid = access.audience === "private" ? access.private_owner !== undefined && access.allowed_entra_groups !== undefined && groups.length === 0 && !access.identity_types?.includes("dsr") : access.private_owner === undefined && groups.length > 0;
|
|
2581
|
+
if (!valid)
|
|
2582
|
+
ctx.addIssue({ code: "custom", path: ["audience"], message: "Private access requires a verified private_owner, no groups, and employee identity; group access requires at least one group and no private_owner." });
|
|
2583
|
+
}
|
|
2584
|
+
|
|
2585
|
+
// ../launchpad-engine/dist/schema.js
|
|
2574
2586
|
var APP_TYPES2 = ["static", "react", "react+api", "container"];
|
|
2575
2587
|
var AUTH_MODES = ["access", "gateway"];
|
|
2576
2588
|
var IDENTITY_TYPES = ["employee", "dsr"];
|
|
2577
|
-
var IdentityTypesSchema =
|
|
2589
|
+
var IdentityTypesSchema = z2.array(z2.enum(IDENTITY_TYPES)).min(1).refine((values) => new Set(values).size === values.length, {
|
|
2578
2590
|
message: "identity_types must not contain duplicates"
|
|
2579
2591
|
}).refine((values) => values.includes("employee"), {
|
|
2580
2592
|
message: "identity_types must include employee"
|
|
@@ -2586,111 +2598,115 @@ var PRIMARY_TARGET = "primary";
|
|
|
2586
2598
|
var SLUG_REGEX = /^[a-z0-9][a-z0-9-]*[a-z0-9]$/;
|
|
2587
2599
|
var ENV_NAME_REGEX = /^[A-Z][A-Z0-9_]*$/;
|
|
2588
2600
|
var SESSION_DURATION_REGEX = /^[0-9]+(s|m|h)$/;
|
|
2589
|
-
var MetadataSchema =
|
|
2590
|
-
name:
|
|
2591
|
-
team:
|
|
2592
|
-
owner:
|
|
2593
|
-
description:
|
|
2601
|
+
var MetadataSchema = z2.object({
|
|
2602
|
+
name: z2.string().min(2).max(63).regex(SLUG_REGEX, "must be lowercase letters, digits, and hyphens; start/end alphanumeric"),
|
|
2603
|
+
team: z2.string().min(1),
|
|
2604
|
+
owner: z2.string().email(),
|
|
2605
|
+
description: z2.string().max(280).optional()
|
|
2594
2606
|
}).strict();
|
|
2595
|
-
var DeploymentSchema =
|
|
2596
|
-
type:
|
|
2597
|
-
runtime:
|
|
2607
|
+
var DeploymentSchema = z2.object({
|
|
2608
|
+
type: z2.enum(APP_TYPES2),
|
|
2609
|
+
runtime: z2.enum(RUNTIMES).optional()
|
|
2598
2610
|
}).strict();
|
|
2599
|
-
var AccessSchema =
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2611
|
+
var AccessSchema = z2.object({
|
|
2612
|
+
audience: z2.literal("private").optional(),
|
|
2613
|
+
private_owner: PrivateOwnerSchema.optional(),
|
|
2614
|
+
allowed_entra_group: z2.string().min(1).optional(),
|
|
2615
|
+
allowed_entra_groups: z2.array(z2.string().min(1)).refine((arr) => new Set(arr).size === arr.length, { message: "allowed_entra_groups must not contain duplicates" }).optional(),
|
|
2616
|
+
session_duration: z2.string().regex(SESSION_DURATION_REGEX, "must match Go duration format, e.g. 24h / 30m / 60s").optional(),
|
|
2603
2617
|
identity_types: IdentityTypesSchema.optional()
|
|
2604
2618
|
}).strict().superRefine((access, ctx) => {
|
|
2605
2619
|
const hasSingular = access.allowed_entra_group !== undefined;
|
|
2606
2620
|
const hasPlural = access.allowed_entra_groups !== undefined;
|
|
2607
|
-
if (!hasSingular && !hasPlural) {
|
|
2621
|
+
if (!hasSingular && !hasPlural && access.audience !== "private") {
|
|
2608
2622
|
ctx.addIssue({
|
|
2609
|
-
code:
|
|
2623
|
+
code: z2.ZodIssueCode.custom,
|
|
2610
2624
|
message: "either `allowed_entra_group` (singular, legacy) or `allowed_entra_groups` (plural, preferred) must be set",
|
|
2611
2625
|
path: ["allowed_entra_groups"]
|
|
2612
2626
|
});
|
|
2613
2627
|
}
|
|
2614
2628
|
if (hasSingular && hasPlural) {
|
|
2615
2629
|
ctx.addIssue({
|
|
2616
|
-
code:
|
|
2630
|
+
code: z2.ZodIssueCode.custom,
|
|
2617
2631
|
message: "`allowed_entra_group` (singular) and `allowed_entra_groups` (plural) are mutually exclusive; use the plural form",
|
|
2618
2632
|
path: ["allowed_entra_groups"]
|
|
2619
2633
|
});
|
|
2620
2634
|
}
|
|
2621
|
-
});
|
|
2635
|
+
}).superRefine(validateAudience);
|
|
2622
2636
|
function allowedEntraGroups(access) {
|
|
2637
|
+
if (access.audience === "private")
|
|
2638
|
+
return [];
|
|
2623
2639
|
if (access.allowed_entra_groups !== undefined) {
|
|
2624
2640
|
return access.allowed_entra_groups;
|
|
2625
2641
|
}
|
|
2626
2642
|
return [access.allowed_entra_group];
|
|
2627
2643
|
}
|
|
2628
|
-
var ContainerSchema =
|
|
2629
|
-
name:
|
|
2630
|
-
image:
|
|
2631
|
-
port:
|
|
2632
|
-
ingress:
|
|
2633
|
-
healthcheck:
|
|
2634
|
-
env:
|
|
2635
|
-
max_instances:
|
|
2644
|
+
var ContainerSchema = z2.object({
|
|
2645
|
+
name: z2.string().min(2).max(32).regex(SLUG_REGEX, "container name must be lowercase letters, digits, and hyphens"),
|
|
2646
|
+
image: z2.string().min(1),
|
|
2647
|
+
port: z2.number().int().min(1).max(65535),
|
|
2648
|
+
ingress: z2.boolean().optional(),
|
|
2649
|
+
healthcheck: z2.string().default("/health"),
|
|
2650
|
+
env: z2.array(z2.string().regex(ENV_NAME_REGEX, "env entries must be UPPER_SNAKE_CASE")).optional(),
|
|
2651
|
+
max_instances: z2.number().int().min(1).max(100).optional()
|
|
2636
2652
|
}).strict();
|
|
2637
|
-
var TargetSchema =
|
|
2638
|
-
kind:
|
|
2639
|
-
script:
|
|
2640
|
-
schedule:
|
|
2641
|
-
d1_binding:
|
|
2653
|
+
var TargetSchema = z2.object({
|
|
2654
|
+
kind: z2.enum(TARGET_KINDS),
|
|
2655
|
+
script: z2.string().min(2).max(63).regex(SLUG_REGEX, "target script must be lowercase letters, digits, and hyphens").optional(),
|
|
2656
|
+
schedule: z2.array(z2.string().min(1).max(120)).min(1).max(20).optional(),
|
|
2657
|
+
d1_binding: z2.string().min(1).max(64).regex(ENV_NAME_REGEX, "d1_binding must be an UPPER_SNAKE_CASE binding name").optional()
|
|
2642
2658
|
}).strict();
|
|
2643
|
-
var SecretBindingSchema =
|
|
2644
|
-
name:
|
|
2645
|
-
source:
|
|
2646
|
-
description:
|
|
2647
|
-
targets:
|
|
2659
|
+
var SecretBindingSchema = z2.object({
|
|
2660
|
+
name: z2.string().min(1).max(64).regex(ENV_NAME_REGEX, "secret binding name must be UPPER_SNAKE_CASE"),
|
|
2661
|
+
source: z2.enum(SECRET_SOURCES),
|
|
2662
|
+
description: z2.string().max(200).optional(),
|
|
2663
|
+
targets: z2.union([z2.literal("all"), z2.array(z2.string().min(1)).min(1)]).optional()
|
|
2648
2664
|
}).strict();
|
|
2649
|
-
var SecretsSchema =
|
|
2650
|
-
bindings:
|
|
2665
|
+
var SecretsSchema = z2.object({
|
|
2666
|
+
bindings: z2.array(SecretBindingSchema).optional()
|
|
2651
2667
|
}).strict();
|
|
2652
|
-
var BuildSchema =
|
|
2653
|
-
command:
|
|
2654
|
-
destination_dir:
|
|
2655
|
-
root_dir:
|
|
2668
|
+
var BuildSchema = z2.object({
|
|
2669
|
+
command: z2.string().min(1).optional(),
|
|
2670
|
+
destination_dir: z2.string().min(1),
|
|
2671
|
+
root_dir: z2.string()
|
|
2656
2672
|
}).strict();
|
|
2657
2673
|
var STATIC_SERVE_DIR = "static";
|
|
2658
2674
|
function staticNoBuild() {
|
|
2659
2675
|
return { destination_dir: STATIC_SERVE_DIR, root_dir: "" };
|
|
2660
2676
|
}
|
|
2661
|
-
var AppBoundarySchema =
|
|
2662
|
-
root:
|
|
2663
|
-
include:
|
|
2664
|
-
exclude:
|
|
2677
|
+
var AppBoundarySchema = z2.object({
|
|
2678
|
+
root: z2.string().min(1).optional(),
|
|
2679
|
+
include: z2.array(z2.string().min(1)).min(1).optional(),
|
|
2680
|
+
exclude: z2.array(z2.string().min(1)).optional()
|
|
2665
2681
|
}).strict();
|
|
2666
|
-
var ProductionEnvSchema =
|
|
2667
|
-
var VerifySchema =
|
|
2668
|
-
protected_path:
|
|
2682
|
+
var ProductionEnvSchema = z2.record(z2.string().regex(ENV_NAME_REGEX, "production_env keys must be UPPER_SNAKE_CASE"), z2.string());
|
|
2683
|
+
var VerifySchema = z2.object({
|
|
2684
|
+
protected_path: z2.string().min(1).regex(/^\//, "verify.protected_path must be a root-relative path beginning with '/'")
|
|
2669
2685
|
}).strict();
|
|
2670
|
-
var ManifestSchema =
|
|
2671
|
-
apiVersion:
|
|
2672
|
-
kind:
|
|
2686
|
+
var ManifestSchema = z2.object({
|
|
2687
|
+
apiVersion: z2.literal("launchpad.m-kopa.us/v1alpha1"),
|
|
2688
|
+
kind: z2.literal("App"),
|
|
2673
2689
|
metadata: MetadataSchema,
|
|
2674
2690
|
deployment: DeploymentSchema,
|
|
2675
2691
|
access: AccessSchema,
|
|
2676
|
-
auth:
|
|
2677
|
-
hostnames:
|
|
2678
|
-
containers:
|
|
2692
|
+
auth: z2.enum(AUTH_MODES).optional(),
|
|
2693
|
+
hostnames: z2.array(z2.string().min(1)).optional(),
|
|
2694
|
+
containers: z2.array(ContainerSchema).min(1).optional(),
|
|
2679
2695
|
secrets: SecretsSchema.optional(),
|
|
2680
|
-
targets:
|
|
2696
|
+
targets: z2.array(TargetSchema).optional(),
|
|
2681
2697
|
build: BuildSchema.optional(),
|
|
2682
2698
|
production_env: ProductionEnvSchema.optional(),
|
|
2683
2699
|
app: AppBoundarySchema.optional(),
|
|
2684
2700
|
verify: VerifySchema.optional(),
|
|
2685
|
-
confine_origin:
|
|
2686
|
-
origin:
|
|
2687
|
-
catalogue:
|
|
2688
|
-
sensitive:
|
|
2701
|
+
confine_origin: z2.boolean().optional(),
|
|
2702
|
+
origin: z2.enum(["pages", "dispatch"]).optional(),
|
|
2703
|
+
catalogue: z2.object({ listed: z2.boolean().default(true) }).strict().optional(),
|
|
2704
|
+
sensitive: z2.boolean().optional()
|
|
2689
2705
|
}).strict().superRefine((m, ctx) => {
|
|
2690
2706
|
const isContainer = m.deployment.type === "container";
|
|
2691
2707
|
if (m.access.identity_types !== undefined && (m.auth ?? "access") !== "gateway") {
|
|
2692
2708
|
ctx.addIssue({
|
|
2693
|
-
code:
|
|
2709
|
+
code: z2.ZodIssueCode.custom,
|
|
2694
2710
|
path: ["access", "identity_types"],
|
|
2695
2711
|
message: "access.identity_types is only valid with auth: gateway"
|
|
2696
2712
|
});
|
|
@@ -2698,19 +2714,19 @@ var ManifestSchema = z.object({
|
|
|
2698
2714
|
if (m.confine_origin !== undefined) {
|
|
2699
2715
|
if (isContainer) {
|
|
2700
2716
|
ctx.addIssue({
|
|
2701
|
-
code:
|
|
2717
|
+
code: z2.ZodIssueCode.custom,
|
|
2702
2718
|
path: ["confine_origin"],
|
|
2703
2719
|
message: "confine_origin is only valid for gateway pages apps (static/react/react+api); container apps have no pages.dev surface."
|
|
2704
2720
|
});
|
|
2705
2721
|
} else if (m.auth === "access") {
|
|
2706
2722
|
ctx.addIssue({
|
|
2707
|
-
code:
|
|
2723
|
+
code: z2.ZodIssueCode.custom,
|
|
2708
2724
|
path: ["confine_origin"],
|
|
2709
2725
|
message: "confine_origin applies to gateway-fronted apps (auth: gateway); it has no meaning under Cloudflare Access."
|
|
2710
2726
|
});
|
|
2711
2727
|
} else if (m.deployment.type === "static" && m.confine_origin === false) {
|
|
2712
2728
|
ctx.addIssue({
|
|
2713
|
-
code:
|
|
2729
|
+
code: z2.ZodIssueCode.custom,
|
|
2714
2730
|
path: ["confine_origin"],
|
|
2715
2731
|
message: "confine_origin: false is not allowed for static apps — they have no verifier, so an unconfined apex would be world-readable (ADR 0031)."
|
|
2716
2732
|
});
|
|
@@ -2718,42 +2734,42 @@ var ManifestSchema = z.object({
|
|
|
2718
2734
|
}
|
|
2719
2735
|
if (isContainer && m.auth === "gateway") {
|
|
2720
2736
|
ctx.addIssue({
|
|
2721
|
-
code:
|
|
2737
|
+
code: z2.ZodIssueCode.custom,
|
|
2722
2738
|
path: ["auth"],
|
|
2723
2739
|
message: "auth: gateway is only supported for pages-app shapes (static/react/react+api); container apps stay on Cloudflare Access (ADR 0016)."
|
|
2724
2740
|
});
|
|
2725
2741
|
}
|
|
2726
2742
|
if (isContainer && m.containers === undefined) {
|
|
2727
2743
|
ctx.addIssue({
|
|
2728
|
-
code:
|
|
2744
|
+
code: z2.ZodIssueCode.custom,
|
|
2729
2745
|
path: ["containers"],
|
|
2730
2746
|
message: "containers[] is required when deployment.type is 'container'"
|
|
2731
2747
|
});
|
|
2732
2748
|
}
|
|
2733
2749
|
if (!isContainer && m.containers !== undefined) {
|
|
2734
2750
|
ctx.addIssue({
|
|
2735
|
-
code:
|
|
2751
|
+
code: z2.ZodIssueCode.custom,
|
|
2736
2752
|
path: ["containers"],
|
|
2737
2753
|
message: `containers[] is only allowed when deployment.type is 'container' (current type: '${m.deployment.type}')`
|
|
2738
2754
|
});
|
|
2739
2755
|
}
|
|
2740
2756
|
if (!isContainer && m.deployment.runtime !== undefined) {
|
|
2741
2757
|
ctx.addIssue({
|
|
2742
|
-
code:
|
|
2758
|
+
code: z2.ZodIssueCode.custom,
|
|
2743
2759
|
path: ["deployment", "runtime"],
|
|
2744
2760
|
message: "deployment.runtime is only meaningful when deployment.type is 'container'"
|
|
2745
2761
|
});
|
|
2746
2762
|
}
|
|
2747
2763
|
if (isContainer && m.build !== undefined) {
|
|
2748
2764
|
ctx.addIssue({
|
|
2749
|
-
code:
|
|
2765
|
+
code: z2.ZodIssueCode.custom,
|
|
2750
2766
|
path: ["build"],
|
|
2751
2767
|
message: "build is only allowed for pages-app deployments (static/react/react+api). Container builds live in the Dockerfile."
|
|
2752
2768
|
});
|
|
2753
2769
|
}
|
|
2754
2770
|
if (isContainer && m.production_env !== undefined) {
|
|
2755
2771
|
ctx.addIssue({
|
|
2756
|
-
code:
|
|
2772
|
+
code: z2.ZodIssueCode.custom,
|
|
2757
2773
|
path: ["production_env"],
|
|
2758
2774
|
message: "production_env is only allowed for pages-app deployments. Container env vars use containers[].env (names only) + the bot's secrets pipeline for values."
|
|
2759
2775
|
});
|
|
@@ -2762,7 +2778,7 @@ var ManifestSchema = z.object({
|
|
|
2762
2778
|
const ingressContainers = m.containers.filter((c) => c.ingress === true);
|
|
2763
2779
|
if (ingressContainers.length !== 1) {
|
|
2764
2780
|
ctx.addIssue({
|
|
2765
|
-
code:
|
|
2781
|
+
code: z2.ZodIssueCode.custom,
|
|
2766
2782
|
path: ["containers"],
|
|
2767
2783
|
message: `multi-container apps must mark exactly one container ingress: true (found ${ingressContainers.length})`
|
|
2768
2784
|
});
|
|
@@ -2775,7 +2791,7 @@ var ManifestSchema = z.object({
|
|
|
2775
2791
|
for (const envName of c.env ?? []) {
|
|
2776
2792
|
if (!declaredBindings.has(envName)) {
|
|
2777
2793
|
ctx.addIssue({
|
|
2778
|
-
code:
|
|
2794
|
+
code: z2.ZodIssueCode.custom,
|
|
2779
2795
|
path: ["containers", i, "env"],
|
|
2780
2796
|
message: `env reference '${envName}' has no matching entry in secrets.bindings[]`
|
|
2781
2797
|
});
|
|
@@ -2791,7 +2807,7 @@ var ManifestSchema = z.object({
|
|
|
2791
2807
|
continue;
|
|
2792
2808
|
if (seen.has(name)) {
|
|
2793
2809
|
ctx.addIssue({
|
|
2794
|
-
code:
|
|
2810
|
+
code: z2.ZodIssueCode.custom,
|
|
2795
2811
|
path: ["containers", i, "name"],
|
|
2796
2812
|
message: `duplicate container name '${name}' — names become DO class identifiers and must be unique`
|
|
2797
2813
|
});
|
|
@@ -2808,7 +2824,7 @@ var ManifestSchema = z.object({
|
|
|
2808
2824
|
continue;
|
|
2809
2825
|
if (seen.has(name)) {
|
|
2810
2826
|
ctx.addIssue({
|
|
2811
|
-
code:
|
|
2827
|
+
code: z2.ZodIssueCode.custom,
|
|
2812
2828
|
path: ["secrets", "bindings", i, "name"],
|
|
2813
2829
|
message: `duplicate secret binding name '${name}' — names become env-var keys and must be unique`
|
|
2814
2830
|
});
|
|
@@ -2821,21 +2837,21 @@ var ManifestSchema = z.object({
|
|
|
2821
2837
|
const t = targets[i];
|
|
2822
2838
|
if (t.kind === "worker" && t.script === undefined) {
|
|
2823
2839
|
ctx.addIssue({
|
|
2824
|
-
code:
|
|
2840
|
+
code: z2.ZodIssueCode.custom,
|
|
2825
2841
|
path: ["targets", i, "script"],
|
|
2826
2842
|
message: "a worker target must declare a script (the sibling Workers script name)"
|
|
2827
2843
|
});
|
|
2828
2844
|
}
|
|
2829
2845
|
if (t.kind === "pages" && t.script !== undefined) {
|
|
2830
2846
|
ctx.addIssue({
|
|
2831
|
-
code:
|
|
2847
|
+
code: z2.ZodIssueCode.custom,
|
|
2832
2848
|
path: ["targets", i, "script"],
|
|
2833
2849
|
message: "a pages target must not declare a script — the primary Pages project is the app slug"
|
|
2834
2850
|
});
|
|
2835
2851
|
}
|
|
2836
2852
|
if (t.kind === "pages" && t.schedule !== undefined) {
|
|
2837
2853
|
ctx.addIssue({
|
|
2838
|
-
code:
|
|
2854
|
+
code: z2.ZodIssueCode.custom,
|
|
2839
2855
|
path: ["targets", i, "schedule"],
|
|
2840
2856
|
message: "schedule is only valid on a worker target (the cron deploy contract — ADR 0022)"
|
|
2841
2857
|
});
|
|
@@ -2849,7 +2865,7 @@ var ManifestSchema = z.object({
|
|
|
2849
2865
|
pagesTargets++;
|
|
2850
2866
|
if (pagesTargets > 1) {
|
|
2851
2867
|
ctx.addIssue({
|
|
2852
|
-
code:
|
|
2868
|
+
code: z2.ZodIssueCode.custom,
|
|
2853
2869
|
path: ["targets", i, "kind"],
|
|
2854
2870
|
message: "at most one pages target may be declared (the primary Pages project is singular)"
|
|
2855
2871
|
});
|
|
@@ -2860,7 +2876,7 @@ var ManifestSchema = z.object({
|
|
|
2860
2876
|
continue;
|
|
2861
2877
|
if (t.script === PRIMARY_TARGET || t.script === "pages") {
|
|
2862
2878
|
ctx.addIssue({
|
|
2863
|
-
code:
|
|
2879
|
+
code: z2.ZodIssueCode.custom,
|
|
2864
2880
|
path: ["targets", i, "script"],
|
|
2865
2881
|
message: `worker target script '${t.script}' is a reserved selector token — use a different script name`
|
|
2866
2882
|
});
|
|
@@ -2868,7 +2884,7 @@ var ManifestSchema = z.object({
|
|
|
2868
2884
|
}
|
|
2869
2885
|
if (seenScripts.has(t.script)) {
|
|
2870
2886
|
ctx.addIssue({
|
|
2871
|
-
code:
|
|
2887
|
+
code: z2.ZodIssueCode.custom,
|
|
2872
2888
|
path: ["targets", i, "script"],
|
|
2873
2889
|
message: `duplicate worker target script '${t.script}' — target scripts must be unique`
|
|
2874
2890
|
});
|
|
@@ -2886,7 +2902,7 @@ var ManifestSchema = z.object({
|
|
|
2886
2902
|
for (const ref of sel) {
|
|
2887
2903
|
if (!validRefs.has(ref)) {
|
|
2888
2904
|
ctx.addIssue({
|
|
2889
|
-
code:
|
|
2905
|
+
code: z2.ZodIssueCode.custom,
|
|
2890
2906
|
path: ["secrets", "bindings", bi, "targets"],
|
|
2891
2907
|
message: `secret binding '${b?.name}' references unknown target '${ref}' — declare it in targets[] or use "primary"`
|
|
2892
2908
|
});
|
|
@@ -3302,42 +3318,44 @@ var STATUS_STATUS_KEYS = new Set([
|
|
|
3302
3318
|
"status.generated_at"
|
|
3303
3319
|
]);
|
|
3304
3320
|
// ../launchpad-engine/dist/schema-v2.js
|
|
3305
|
-
import { z as
|
|
3306
|
-
var EnvVarSchema =
|
|
3307
|
-
|
|
3308
|
-
|
|
3321
|
+
import { z as z3 } from "zod";
|
|
3322
|
+
var EnvVarSchema = z3.discriminatedUnion("type", [
|
|
3323
|
+
z3.object({ type: z3.literal("secret_text"), secret_ref: z3.string().min(1) }).strict(),
|
|
3324
|
+
z3.object({ type: z3.literal("plain_text"), value: z3.string() }).strict()
|
|
3309
3325
|
]);
|
|
3310
|
-
var SpecSchema =
|
|
3311
|
-
appType:
|
|
3312
|
-
display:
|
|
3313
|
-
name:
|
|
3314
|
-
description:
|
|
3315
|
-
team:
|
|
3326
|
+
var SpecSchema = z3.object({
|
|
3327
|
+
appType: z3.enum(APP_TYPES2),
|
|
3328
|
+
display: z3.object({
|
|
3329
|
+
name: z3.string().min(1),
|
|
3330
|
+
description: z3.string().max(280).optional(),
|
|
3331
|
+
team: z3.string().min(1)
|
|
3316
3332
|
}).strict(),
|
|
3317
|
-
owner:
|
|
3318
|
-
access:
|
|
3319
|
-
|
|
3333
|
+
owner: z3.object({ email: z3.string().email() }).strict(),
|
|
3334
|
+
access: z3.object({
|
|
3335
|
+
audience: z3.literal("private").optional(),
|
|
3336
|
+
private_owner: PrivateOwnerSchema.optional(),
|
|
3337
|
+
allowed_entra_groups: z3.array(z3.string().min(1)).refine((a) => new Set(a).size === a.length, {
|
|
3320
3338
|
message: "allowed_entra_groups must not contain duplicates"
|
|
3321
3339
|
}),
|
|
3322
|
-
require_email_domain:
|
|
3340
|
+
require_email_domain: z3.string().min(1).optional(),
|
|
3323
3341
|
identity_types: IdentityTypesSchema.optional()
|
|
3324
|
-
}).strict(),
|
|
3325
|
-
auth:
|
|
3342
|
+
}).strict().superRefine(validateAudience),
|
|
3343
|
+
auth: z3.enum(AUTH_MODES).optional(),
|
|
3326
3344
|
build: BuildSchema.optional(),
|
|
3327
3345
|
app: AppBoundarySchema.optional(),
|
|
3328
3346
|
verify: VerifySchema.optional(),
|
|
3329
|
-
confine_origin:
|
|
3330
|
-
origin:
|
|
3331
|
-
sensitive:
|
|
3332
|
-
catalogue:
|
|
3333
|
-
env_vars:
|
|
3334
|
-
containers:
|
|
3347
|
+
confine_origin: z3.boolean().optional(),
|
|
3348
|
+
origin: z3.enum(["pages", "dispatch"]).optional(),
|
|
3349
|
+
sensitive: z3.boolean().optional(),
|
|
3350
|
+
catalogue: z3.object({ listed: z3.boolean().default(true) }).strict().optional(),
|
|
3351
|
+
env_vars: z3.record(z3.string().regex(ENV_NAME_REGEX, "env_vars keys must be UPPER_SNAKE_CASE"), EnvVarSchema).optional(),
|
|
3352
|
+
containers: z3.array(ContainerSchema).min(1).optional(),
|
|
3335
3353
|
secrets: SecretsSchema.optional(),
|
|
3336
|
-
fronted_by_concierge:
|
|
3354
|
+
fronted_by_concierge: z3.boolean().optional()
|
|
3337
3355
|
}).strict().superRefine((s, ctx) => {
|
|
3338
3356
|
if (s.access.identity_types !== undefined && (s.auth ?? "access") !== "gateway") {
|
|
3339
3357
|
ctx.addIssue({
|
|
3340
|
-
code:
|
|
3358
|
+
code: z3.ZodIssueCode.custom,
|
|
3341
3359
|
path: ["access", "identity_types"],
|
|
3342
3360
|
message: "access.identity_types is only valid with auth: gateway"
|
|
3343
3361
|
});
|
|
@@ -3345,7 +3363,7 @@ var SpecSchema = z2.object({
|
|
|
3345
3363
|
for (const [k, v] of Object.entries(s.env_vars ?? {})) {
|
|
3346
3364
|
if (/_(TOKEN|SECRET)$/.test(k) && v.type !== "secret_text") {
|
|
3347
3365
|
ctx.addIssue({
|
|
3348
|
-
code:
|
|
3366
|
+
code: z3.ZodIssueCode.custom,
|
|
3349
3367
|
path: ["env_vars", k],
|
|
3350
3368
|
message: `env var '${k}' is secret-bearing (*_TOKEN/*_SECRET) and must be type secret_text, not '${v.type}'`
|
|
3351
3369
|
});
|
|
@@ -3354,7 +3372,7 @@ var SpecSchema = z2.object({
|
|
|
3354
3372
|
const isContainer = s.appType === "container";
|
|
3355
3373
|
if (isContainer && s.containers === undefined) {
|
|
3356
3374
|
ctx.addIssue({
|
|
3357
|
-
code:
|
|
3375
|
+
code: z3.ZodIssueCode.custom,
|
|
3358
3376
|
path: ["containers"],
|
|
3359
3377
|
message: "containers[] is required when appType is 'container'"
|
|
3360
3378
|
});
|
|
@@ -3362,19 +3380,19 @@ var SpecSchema = z2.object({
|
|
|
3362
3380
|
if (s.confine_origin !== undefined) {
|
|
3363
3381
|
if (isContainer) {
|
|
3364
3382
|
ctx.addIssue({
|
|
3365
|
-
code:
|
|
3383
|
+
code: z3.ZodIssueCode.custom,
|
|
3366
3384
|
path: ["confine_origin"],
|
|
3367
3385
|
message: "confine_origin is only valid for gateway pages apps; container apps have no pages.dev surface."
|
|
3368
3386
|
});
|
|
3369
3387
|
} else if (s.auth === "access") {
|
|
3370
3388
|
ctx.addIssue({
|
|
3371
|
-
code:
|
|
3389
|
+
code: z3.ZodIssueCode.custom,
|
|
3372
3390
|
path: ["confine_origin"],
|
|
3373
3391
|
message: "confine_origin applies to gateway-fronted apps (auth: gateway); it has no meaning under Cloudflare Access."
|
|
3374
3392
|
});
|
|
3375
3393
|
} else if (s.appType === "static" && s.confine_origin === false) {
|
|
3376
3394
|
ctx.addIssue({
|
|
3377
|
-
code:
|
|
3395
|
+
code: z3.ZodIssueCode.custom,
|
|
3378
3396
|
path: ["confine_origin"],
|
|
3379
3397
|
message: "confine_origin: false is not allowed for static apps — an unconfined apex with no verifier is world-readable (ADR 0031)."
|
|
3380
3398
|
});
|
|
@@ -3383,21 +3401,21 @@ var SpecSchema = z2.object({
|
|
|
3383
3401
|
if (s.origin === "dispatch") {
|
|
3384
3402
|
if (isContainer) {
|
|
3385
3403
|
ctx.addIssue({
|
|
3386
|
-
code:
|
|
3404
|
+
code: z3.ZodIssueCode.custom,
|
|
3387
3405
|
path: ["origin"],
|
|
3388
3406
|
message: "origin: dispatch is not available for container apps — they run on the container runtime (ADR 0015/0016), not in a Workers dispatch namespace."
|
|
3389
3407
|
});
|
|
3390
3408
|
}
|
|
3391
3409
|
if ((s.auth ?? "access") !== "gateway") {
|
|
3392
3410
|
ctx.addIssue({
|
|
3393
|
-
code:
|
|
3411
|
+
code: z3.ZodIssueCode.custom,
|
|
3394
3412
|
path: ["origin"],
|
|
3395
3413
|
message: "origin: dispatch requires an explicit auth: gateway (auth defaults to 'access' when omitted). A dispatch-namespace Worker has no public hostname for Cloudflare Access to gate, so the gateway must be the sole gate."
|
|
3396
3414
|
});
|
|
3397
3415
|
}
|
|
3398
3416
|
if (s.confine_origin !== undefined) {
|
|
3399
3417
|
ctx.addIssue({
|
|
3400
|
-
code:
|
|
3418
|
+
code: z3.ZodIssueCode.custom,
|
|
3401
3419
|
path: ["confine_origin"],
|
|
3402
3420
|
message: "confine_origin has no meaning with origin: dispatch — there is no public pages.dev apex to confine. Remove it."
|
|
3403
3421
|
});
|
|
@@ -3405,7 +3423,7 @@ var SpecSchema = z2.object({
|
|
|
3405
3423
|
for (const [name, envVar] of Object.entries(s.env_vars ?? {})) {
|
|
3406
3424
|
if (envVar.type === "secret_text") {
|
|
3407
3425
|
ctx.addIssue({
|
|
3408
|
-
code:
|
|
3426
|
+
code: z3.ZodIssueCode.custom,
|
|
3409
3427
|
path: ["env_vars", name],
|
|
3410
3428
|
message: "secret_text env vars are not supported with origin: dispatch until PS-2740 (secrets on v2) provides their runtime consumer."
|
|
3411
3429
|
});
|
|
@@ -3414,93 +3432,93 @@ var SpecSchema = z2.object({
|
|
|
3414
3432
|
}
|
|
3415
3433
|
if (!isContainer && s.containers !== undefined) {
|
|
3416
3434
|
ctx.addIssue({
|
|
3417
|
-
code:
|
|
3435
|
+
code: z3.ZodIssueCode.custom,
|
|
3418
3436
|
path: ["containers"],
|
|
3419
3437
|
message: `containers[] is only allowed when appType is 'container' (current appType: '${s.appType}')`
|
|
3420
3438
|
});
|
|
3421
3439
|
}
|
|
3422
3440
|
if (isContainer && s.build !== undefined) {
|
|
3423
3441
|
ctx.addIssue({
|
|
3424
|
-
code:
|
|
3442
|
+
code: z3.ZodIssueCode.custom,
|
|
3425
3443
|
path: ["build"],
|
|
3426
3444
|
message: "build is not allowed when appType is 'container' (container build lives in the Dockerfile)"
|
|
3427
3445
|
});
|
|
3428
3446
|
}
|
|
3429
3447
|
if (isContainer && s.auth === "gateway") {
|
|
3430
3448
|
ctx.addIssue({
|
|
3431
|
-
code:
|
|
3449
|
+
code: z3.ZodIssueCode.custom,
|
|
3432
3450
|
path: ["auth"],
|
|
3433
3451
|
message: "auth: gateway is only supported for pages-app shapes (static/react/react+api); container apps stay on Cloudflare Access (ADR 0016)."
|
|
3434
3452
|
});
|
|
3435
3453
|
}
|
|
3436
3454
|
if (!isContainer && s.fronted_by_concierge === true) {
|
|
3437
3455
|
ctx.addIssue({
|
|
3438
|
-
code:
|
|
3456
|
+
code: z3.ZodIssueCode.custom,
|
|
3439
3457
|
path: ["fronted_by_concierge"],
|
|
3440
3458
|
message: `fronted_by_concierge is only allowed when appType is 'container' (current appType: '${s.appType}')`
|
|
3441
3459
|
});
|
|
3442
3460
|
}
|
|
3443
3461
|
});
|
|
3444
|
-
var nonEmpty =
|
|
3445
|
-
var StatusSchema =
|
|
3446
|
-
schemaVersion:
|
|
3462
|
+
var nonEmpty = z3.string().min(1);
|
|
3463
|
+
var StatusSchema = z3.object({
|
|
3464
|
+
schemaVersion: z3.literal(2),
|
|
3447
3465
|
manifest_spec_sha: nonEmpty,
|
|
3448
|
-
cloudflare:
|
|
3449
|
-
access:
|
|
3466
|
+
cloudflare: z3.object({
|
|
3467
|
+
access: z3.object({
|
|
3450
3468
|
app_id: nonEmpty,
|
|
3451
3469
|
app_aud: nonEmpty,
|
|
3452
3470
|
team_domain: nonEmpty,
|
|
3453
3471
|
policy_id: nonEmpty
|
|
3454
3472
|
}).strict(),
|
|
3455
|
-
pages:
|
|
3473
|
+
pages: z3.object({
|
|
3456
3474
|
project_name: nonEmpty,
|
|
3457
3475
|
subdomain: nonEmpty,
|
|
3458
3476
|
latest_deployment_id: nonEmpty
|
|
3459
3477
|
}).strict().optional(),
|
|
3460
|
-
worker:
|
|
3478
|
+
worker: z3.object({
|
|
3461
3479
|
dev_url: nonEmpty,
|
|
3462
3480
|
script_name: nonEmpty
|
|
3463
3481
|
}).strict().optional()
|
|
3464
3482
|
}).strict(),
|
|
3465
|
-
dns:
|
|
3466
|
-
terraform:
|
|
3483
|
+
dns: z3.object({ custom_hostname: nonEmpty, cname_target: nonEmpty }).strict().optional(),
|
|
3484
|
+
terraform: z3.object({
|
|
3467
3485
|
workspace_state_key: nonEmpty,
|
|
3468
3486
|
last_applied_at: nonEmpty,
|
|
3469
3487
|
last_applied_sha: nonEmpty
|
|
3470
3488
|
}).strict(),
|
|
3471
|
-
registry:
|
|
3489
|
+
registry: z3.object({ record_present: z3.boolean(), lifecycle: nonEmpty }).strict(),
|
|
3472
3490
|
generated_at: nonEmpty
|
|
3473
3491
|
}).strict().superRefine((s, ctx) => {
|
|
3474
3492
|
const hasPages = s.cloudflare.pages !== undefined;
|
|
3475
3493
|
const hasWorker = s.cloudflare.worker !== undefined;
|
|
3476
3494
|
if (hasPages === hasWorker) {
|
|
3477
3495
|
ctx.addIssue({
|
|
3478
|
-
code:
|
|
3496
|
+
code: z3.ZodIssueCode.custom,
|
|
3479
3497
|
path: ["cloudflare"],
|
|
3480
3498
|
message: "status.cloudflare must carry exactly one of `pages` (pages apps) or `worker` (container apps)"
|
|
3481
3499
|
});
|
|
3482
3500
|
}
|
|
3483
3501
|
if (hasPages && s.dns === undefined) {
|
|
3484
3502
|
ctx.addIssue({
|
|
3485
|
-
code:
|
|
3503
|
+
code: z3.ZodIssueCode.custom,
|
|
3486
3504
|
path: ["dns"],
|
|
3487
3505
|
message: "status.dns is required for a pages-shape status (cloudflare.pages present)"
|
|
3488
3506
|
});
|
|
3489
3507
|
}
|
|
3490
3508
|
if (hasWorker && s.dns !== undefined) {
|
|
3491
3509
|
ctx.addIssue({
|
|
3492
|
-
code:
|
|
3510
|
+
code: z3.ZodIssueCode.custom,
|
|
3493
3511
|
path: ["dns"],
|
|
3494
3512
|
message: "status.dns must be absent for a workers_dev_only container status (no custom hostname)"
|
|
3495
3513
|
});
|
|
3496
3514
|
}
|
|
3497
3515
|
});
|
|
3498
|
-
var ManifestV2Schema =
|
|
3499
|
-
apiVersion:
|
|
3500
|
-
kind:
|
|
3501
|
-
metadata:
|
|
3502
|
-
slug:
|
|
3503
|
-
schemaVersion:
|
|
3516
|
+
var ManifestV2Schema = z3.object({
|
|
3517
|
+
apiVersion: z3.literal("launchpad.m-kopa.us/v2"),
|
|
3518
|
+
kind: z3.literal("App"),
|
|
3519
|
+
metadata: z3.object({
|
|
3520
|
+
slug: z3.string().min(2).max(63).regex(SLUG_REGEX, "must be lowercase letters, digits, and hyphens; start/end alphanumeric"),
|
|
3521
|
+
schemaVersion: z3.literal(2)
|
|
3504
3522
|
}).strict(),
|
|
3505
3523
|
spec: SpecSchema,
|
|
3506
3524
|
status: StatusSchema.optional()
|
|
@@ -4230,6 +4248,11 @@ var BROAD_GROUPS = [
|
|
|
4230
4248
|
uuid: "caf78902-35dd-4833-88ce-8f067eda6036",
|
|
4231
4249
|
name: "S_ALL_Employees",
|
|
4232
4250
|
note: "All employees — the broadest grant on the tenant; assigned to the Launchpad enterprise app. Granting this reaches the entire company."
|
|
4251
|
+
},
|
|
4252
|
+
{
|
|
4253
|
+
uuid: "09c87073-2071-4f8e-9e97-aa67f7a153a7",
|
|
4254
|
+
name: "S_ALL",
|
|
4255
|
+
note: "Broad tenant group. Assignment is not a reliable limit on gateway eligibility."
|
|
4233
4256
|
}
|
|
4234
4257
|
];
|
|
4235
4258
|
function assertBroadGroupsWellFormed(list = BROAD_GROUPS) {
|
|
@@ -4248,7 +4271,7 @@ function assertBroadGroupsWellFormed(list = BROAD_GROUPS) {
|
|
|
4248
4271
|
assertBroadGroupsWellFormed();
|
|
4249
4272
|
var BROAD_UUIDS = new Set(BROAD_GROUPS.map((g) => canonicalUuid(g.uuid)).filter((u) => u !== null));
|
|
4250
4273
|
// ../launchpad-engine/dist/fleet-manifest.js
|
|
4251
|
-
import { z as
|
|
4274
|
+
import { z as z4 } from "zod";
|
|
4252
4275
|
var UUID_RE = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
|
|
4253
4276
|
var TF_RESIDENCIES = ["per-app-workspace", "main-tf"];
|
|
4254
4277
|
var APP_SHAPES = [
|
|
@@ -4276,22 +4299,22 @@ var FLEET_TIERS = [
|
|
|
4276
4299
|
"out-of-scope"
|
|
4277
4300
|
];
|
|
4278
4301
|
var DEVICE_POSTURE = ["gateway-eligible", "retained-access"];
|
|
4279
|
-
var GroupId =
|
|
4280
|
-
var FleetAppSchema =
|
|
4281
|
-
slug:
|
|
4282
|
-
residency:
|
|
4283
|
-
shape:
|
|
4284
|
-
declaredAuth:
|
|
4285
|
-
actualAuth:
|
|
4286
|
-
groups:
|
|
4287
|
-
tier:
|
|
4288
|
-
devicePosture:
|
|
4289
|
-
devicePostureReason:
|
|
4290
|
-
notes:
|
|
4302
|
+
var GroupId = z4.string().regex(UUID_RE, "Entra group ids are emitted as canonical UUIDs in the JWT groups claim — display names will not match policies.");
|
|
4303
|
+
var FleetAppSchema = z4.object({
|
|
4304
|
+
slug: z4.string().regex(SLUG_REGEX),
|
|
4305
|
+
residency: z4.enum(TF_RESIDENCIES),
|
|
4306
|
+
shape: z4.enum(APP_SHAPES),
|
|
4307
|
+
declaredAuth: z4.enum(DECLARED_AUTH),
|
|
4308
|
+
actualAuth: z4.enum(ACTUAL_AUTH),
|
|
4309
|
+
groups: z4.array(GroupId).default([]),
|
|
4310
|
+
tier: z4.enum(FLEET_TIERS),
|
|
4311
|
+
devicePosture: z4.enum(DEVICE_POSTURE),
|
|
4312
|
+
devicePostureReason: z4.string().trim().min(1, "devicePosture is only valid with a non-empty reason — the two-tier determination must be a surveyed output, not a default (AC-T0-3)."),
|
|
4313
|
+
notes: z4.string().optional()
|
|
4291
4314
|
}).strict();
|
|
4292
|
-
var FleetManifestSchema =
|
|
4293
|
-
schemaVersion:
|
|
4294
|
-
apps:
|
|
4315
|
+
var FleetManifestSchema = z4.object({
|
|
4316
|
+
schemaVersion: z4.literal(1),
|
|
4317
|
+
apps: z4.array(FleetAppSchema).min(1)
|
|
4295
4318
|
}).strict().superRefine((m, ctx) => {
|
|
4296
4319
|
const seen = new Set;
|
|
4297
4320
|
for (const app of m.apps) {
|
|
@@ -4313,23 +4336,23 @@ var FleetManifestSchema = z3.object({
|
|
|
4313
4336
|
}
|
|
4314
4337
|
});
|
|
4315
4338
|
// ../launchpad-engine/dist/fleet-secret-sets.js
|
|
4316
|
-
import { z as
|
|
4339
|
+
import { z as z5 } from "zod";
|
|
4317
4340
|
var SET_NAME_REGEX = /^[a-z0-9][a-z0-9-]*[a-z0-9]$/;
|
|
4318
|
-
var Slug =
|
|
4319
|
-
var SecretName =
|
|
4320
|
-
var SecretSetExcludeSchema =
|
|
4341
|
+
var Slug = z5.string().regex(SLUG_REGEX, "member must be a lowercase-kebab app slug");
|
|
4342
|
+
var SecretName = z5.string().regex(ENV_NAME_REGEX, "secret name must be UPPER_SNAKE_CASE");
|
|
4343
|
+
var SecretSetExcludeSchema = z5.object({
|
|
4321
4344
|
member: Slug,
|
|
4322
4345
|
secret: SecretName
|
|
4323
4346
|
}).strict();
|
|
4324
|
-
var SecretSetSchema =
|
|
4325
|
-
name:
|
|
4326
|
-
secrets:
|
|
4327
|
-
members:
|
|
4328
|
-
exclude:
|
|
4347
|
+
var SecretSetSchema = z5.object({
|
|
4348
|
+
name: z5.string().min(1).max(64).regex(SET_NAME_REGEX, "set name must be lowercase-kebab"),
|
|
4349
|
+
secrets: z5.array(SecretName).min(1),
|
|
4350
|
+
members: z5.array(Slug).min(1),
|
|
4351
|
+
exclude: z5.array(SecretSetExcludeSchema).optional()
|
|
4329
4352
|
}).strict();
|
|
4330
|
-
var FleetSecretSetsSchema =
|
|
4331
|
-
schemaVersion:
|
|
4332
|
-
secretSets:
|
|
4353
|
+
var FleetSecretSetsSchema = z5.object({
|
|
4354
|
+
schemaVersion: z5.literal(1),
|
|
4355
|
+
secretSets: z5.array(SecretSetSchema).min(1)
|
|
4333
4356
|
}).strict().superRefine((m, ctx) => {
|
|
4334
4357
|
const seenSets = new Set;
|
|
4335
4358
|
for (let i = 0;i < m.secretSets.length; i++) {
|
|
@@ -4863,7 +4886,7 @@ function toBytes(v) {
|
|
|
4863
4886
|
async function verifyServed(args, deps = { fetch }) {
|
|
4864
4887
|
let res;
|
|
4865
4888
|
try {
|
|
4866
|
-
res = await deps.fetch(args.servedUrl, { method: "GET" });
|
|
4889
|
+
res = await deps.fetch(args.servedUrl, { method: "GET", redirect: "manual" });
|
|
4867
4890
|
} catch (e) {
|
|
4868
4891
|
return {
|
|
4869
4892
|
kind: "unreachable",
|
|
@@ -4874,7 +4897,7 @@ async function verifyServed(args, deps = { fetch }) {
|
|
|
4874
4897
|
return {
|
|
4875
4898
|
kind: "unreachable",
|
|
4876
4899
|
status: res.status,
|
|
4877
|
-
reason: `served URL returned HTTP ${res.status}`
|
|
4900
|
+
reason: [301, 302, 303, 307, 308].includes(res.status) ? `served URL redirected (HTTP ${res.status}); verify the app in an authenticated browser` : `served URL returned HTTP ${res.status}`
|
|
4878
4901
|
};
|
|
4879
4902
|
}
|
|
4880
4903
|
if (args.appType !== "static") {
|
|
@@ -7625,9 +7648,6 @@ function parseDeployFlags(args) {
|
|
|
7625
7648
|
if (appType === null) {
|
|
7626
7649
|
return `--new requires --app-type (one of ${APP_TYPES.join(", ")})`;
|
|
7627
7650
|
}
|
|
7628
|
-
if (allowedGroups.length === 0) {
|
|
7629
|
-
return "--new requires at least one --allowed-group";
|
|
7630
|
-
}
|
|
7631
7651
|
return {
|
|
7632
7652
|
mode: {
|
|
7633
7653
|
kind: "new",
|
|
@@ -9138,6 +9158,11 @@ async function runModelADeploy(args) {
|
|
|
9138
9158
|
allowedHost: cfg.statusPageHost
|
|
9139
9159
|
});
|
|
9140
9160
|
const errorCode = body.error;
|
|
9161
|
+
if (errorCode === "audience_confirmation_required") {
|
|
9162
|
+
io.err(String(body.message));
|
|
9163
|
+
io.err("Only the human builder may confirm. Retry this deployment after confirmation.");
|
|
9164
|
+
return 1;
|
|
9165
|
+
}
|
|
9141
9166
|
if (result.status === 426 && typeof body.minimum === "string" && typeof body.yours === "string") {
|
|
9142
9167
|
io.err(upgradeRequiredMessage(body.minimum, body.yours));
|
|
9143
9168
|
return EXIT_CLI_UPGRADE_REQUIRED;
|
|
@@ -10747,7 +10772,7 @@ var initCommand = {
|
|
|
10747
10772
|
summary: "scaffold a new launchpad.yaml in the current directory",
|
|
10748
10773
|
run: (args, io) => runInit(args, io, defaultPrompt2)
|
|
10749
10774
|
};
|
|
10750
|
-
async function runInit(args, io, prompt) {
|
|
10775
|
+
async function runInit(args, io, prompt, identity = () => apiJson(loadConfig(), { path: "/me/identity" })) {
|
|
10751
10776
|
const parsed = parseArgs6(args);
|
|
10752
10777
|
if (parsed.kind === "usage-error") {
|
|
10753
10778
|
io.err(`launchpad init: ${parsed.message}`);
|
|
@@ -10801,7 +10826,20 @@ async function runInit(args, io, prompt) {
|
|
|
10801
10826
|
io.err("launchpad init: dispatch does not support --hostname or --session-duration; use --origin pages for a Pages manifest");
|
|
10802
10827
|
return 64;
|
|
10803
10828
|
}
|
|
10804
|
-
|
|
10829
|
+
let privateOwner;
|
|
10830
|
+
if (fillResult.inputs.groups.length === 0) {
|
|
10831
|
+
try {
|
|
10832
|
+
privateOwner = await identity();
|
|
10833
|
+
} catch (error) {
|
|
10834
|
+
io.err(`launchpad init: cannot verify your identity for a private app: ${error instanceof Error ? error.message : String(error)}`);
|
|
10835
|
+
return 1;
|
|
10836
|
+
}
|
|
10837
|
+
io.out(`Audience: private. Only you (${privateOwner.email}) and platform administrators can open this app.`);
|
|
10838
|
+
} else {
|
|
10839
|
+
io.out(`Audience: selected Entra groups (${fillResult.inputs.groups.join(", ")}), plus platform administrators.`);
|
|
10840
|
+
io.out("Company-wide grants require your explicit browser confirmation before publishing.");
|
|
10841
|
+
}
|
|
10842
|
+
const manifest = buildManifest({ ...fillResult.inputs, ...privateOwner === undefined ? {} : { privateOwner } }, detected);
|
|
10805
10843
|
const validation = manifest.apiVersion === "launchpad.m-kopa.us/v2" ? ManifestV2Schema.safeParse(manifest) : ManifestSchema.safeParse(manifest);
|
|
10806
10844
|
if (!validation.success) {
|
|
10807
10845
|
io.err("launchpad init: invalid manifest input");
|
|
@@ -11101,18 +11139,7 @@ async function fillRequired(inputs, options, io, prompt) {
|
|
|
11101
11139
|
io.err(` must be one of: ${APP_TYPES2.join(", ")}`);
|
|
11102
11140
|
}
|
|
11103
11141
|
}
|
|
11104
|
-
|
|
11105
|
-
if (inputs.groups.length > 0) {
|
|
11106
|
-
groupsR = inputs.groups;
|
|
11107
|
-
} else {
|
|
11108
|
-
const raw = await askIfMissing(undefined, "allowed_entra_groups (one or more, comma-separated)", (v) => v.length >= 1 ? undefined : "must not be empty");
|
|
11109
|
-
if (typeof raw !== "string")
|
|
11110
|
-
return raw;
|
|
11111
|
-
groupsR = raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
11112
|
-
if (groupsR.length === 0) {
|
|
11113
|
-
return { kind: "usage-error", message: "at least one allowed Entra group is required" };
|
|
11114
|
-
}
|
|
11115
|
-
}
|
|
11142
|
+
const groupsR = inputs.groups;
|
|
11116
11143
|
let containerImage = inputs.containerImage;
|
|
11117
11144
|
let containerPort = inputs.containerPort;
|
|
11118
11145
|
if (typeR === "container") {
|
|
@@ -11178,7 +11205,7 @@ function buildManifest(inputs, detected) {
|
|
|
11178
11205
|
metadata.description = inputs.description;
|
|
11179
11206
|
}
|
|
11180
11207
|
const deployment = { type: inputs.type };
|
|
11181
|
-
const access = inputs.groups.length === 1 ? { allowed_entra_group: inputs.groups[0] } : { allowed_entra_groups: [...inputs.groups] };
|
|
11208
|
+
const access = inputs.privateOwner !== undefined ? { audience: "private", private_owner: inputs.privateOwner, allowed_entra_groups: [] } : inputs.groups.length === 1 ? { allowed_entra_group: inputs.groups[0] } : { allowed_entra_groups: [...inputs.groups] };
|
|
11182
11209
|
if (inputs.sessionDuration !== undefined && inputs.sessionDuration !== "") {
|
|
11183
11210
|
access.session_duration = inputs.sessionDuration;
|
|
11184
11211
|
}
|
|
@@ -11187,6 +11214,7 @@ function buildManifest(inputs, detected) {
|
|
|
11187
11214
|
kind: "App",
|
|
11188
11215
|
metadata,
|
|
11189
11216
|
deployment,
|
|
11217
|
+
catalogue: { listed: false },
|
|
11190
11218
|
access
|
|
11191
11219
|
};
|
|
11192
11220
|
const resolvedAuth = inputs.auth ?? (inputs.type === "container" ? "access" : "gateway");
|
|
@@ -11234,7 +11262,11 @@ function buildManifest(inputs, detected) {
|
|
|
11234
11262
|
...inputs.description ? { description: inputs.description } : {}
|
|
11235
11263
|
},
|
|
11236
11264
|
owner: { email: inputs.owner },
|
|
11237
|
-
access: {
|
|
11265
|
+
access: {
|
|
11266
|
+
...inputs.privateOwner === undefined ? {} : { audience: "private", private_owner: inputs.privateOwner },
|
|
11267
|
+
allowed_entra_groups: [...inputs.groups]
|
|
11268
|
+
},
|
|
11269
|
+
catalogue: { listed: false },
|
|
11238
11270
|
app: manifest.app,
|
|
11239
11271
|
...manifest.build === undefined ? {} : { build: manifest.build }
|
|
11240
11272
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deploy-flags.d.ts","sourceRoot":"","sources":["../../src/commands/deploy-flags.ts"],"names":[],"mappings":"AA0BA,OAAO,EAAa,KAAK,OAAO,EAAE,MAAM,aAAa,CAAC;AAEtD,MAAM,MAAM,UAAU,GAClB;IACE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,oEAAoE;IACpE,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B;uEACmE;IACnE,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC;gEAC4D;IAC5D,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,yEAAyE;IACzE,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;CAClC,GACD;IACE,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;CAC3C,GACD;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAClD;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE;AACrD;;;;;;;GAOG;GACD;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;CAAE;AACpF;;;;;;;;;;GAUG;GACD;IACE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB;;;;;;OAMG;IACH,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B;;;OAGG;IACH,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;CACxC,CAAC;AAEN,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,uDAAuD;IACvD,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,mEAAmE;IACnE,QAAQ,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC;CACxB;AAQD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,WAAW,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"deploy-flags.d.ts","sourceRoot":"","sources":["../../src/commands/deploy-flags.ts"],"names":[],"mappings":"AA0BA,OAAO,EAAa,KAAK,OAAO,EAAE,MAAM,aAAa,CAAC;AAEtD,MAAM,MAAM,UAAU,GAClB;IACE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,oEAAoE;IACpE,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B;uEACmE;IACnE,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC;gEAC4D;IAC5D,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,yEAAyE;IACzE,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;CAClC,GACD;IACE,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;CAC3C,GACD;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAClD;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE;AACrD;;;;;;;GAOG;GACD;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;CAAE;AACpF;;;;;;;;;;GAUG;GACD;IACE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB;;;;;;OAMG;IACH,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B;;;OAGG;IACH,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;CACxC,CAAC;AAEN,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,uDAAuD;IACvD,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,mEAAmE;IACnE,QAAQ,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC;CACxB;AAQD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,WAAW,GAAG,MAAM,CA6Y9E"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/commands/deploy.ts"],"names":[],"mappings":"AA0EA,OAAO,EAGL,4BAA4B,EAC7B,MAAM,uBAAuB,CAAC;AAY/B,OAAO,EAAa,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACnF,OAAO,KAAK,EAAS,OAAO,EAAY,MAAM,kBAAkB,CAAC;AAQjE,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,CAAC;AAEjD,eAAO,MAAM,aAAa,EAAE,OAI3B,CAAC;AAEF,UAAU,UAAU;IAClB,6BAA6B;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AA6RD;;;;;;GAMG;AACH,wBAAgB,WAAW,IAAI,MAAM,CAoBpC;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,UAAU,GAAG,IAAI,CA4BpE;AAiMD,2BAA2B;AAC3B,OAAO,EAAE,4BAA4B,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/commands/deploy.ts"],"names":[],"mappings":"AA0EA,OAAO,EAGL,4BAA4B,EAC7B,MAAM,uBAAuB,CAAC;AAY/B,OAAO,EAAa,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACnF,OAAO,KAAK,EAAS,OAAO,EAAY,MAAM,kBAAkB,CAAC;AAQjE,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,CAAC;AAEjD,eAAO,MAAM,aAAa,EAAE,OAI3B,CAAC;AAEF,UAAU,UAAU;IAClB,6BAA6B;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AA6RD;;;;;;GAMG;AACH,wBAAgB,WAAW,IAAI,MAAM,CAoBpC;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,UAAU,GAAG,IAAI,CA4BpE;AAiMD,2BAA2B;AAC3B,OAAO,EAAE,4BAA4B,EAAE,CAAC;AAstBxC,2EAA2E;AAC3E;+EAC+E;AAC/E,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,UAAU,GAAG,SAAS,CAAC;AAE9D,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAG,cAAc,CAG/E;AAED;;;4EAG4E;AAC5E,wBAAgB,iBAAiB,CAAC,KAAK,EAAE;IACvC,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC;IAClC,QAAQ,CAAC,QAAQ,EAAE,OAAO,GAAG,UAAU,GAAG,SAAS,CAAC;CACrD,GAAG,OAAO,CAIV;AAED,wBAAgB,uBAAuB,CACrC,cAAc,EAAE,OAAO,EACvB,UAAU,GAAE,OAAO,GAAG,SAAqB,GAC1C,MAAM,CAOR"}
|
package/dist/commands/init.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { CliIo, Command, ExitCode } from "../dispatcher.js";
|
|
2
2
|
import { type AppType, type AuthMode } from "../manifest/schema.js";
|
|
3
|
+
import type { PrivateOwner } from "@m-kopa/launchpad-engine";
|
|
3
4
|
/** Required-and-optional fields the verb consumes. */
|
|
4
5
|
export interface InitInputs {
|
|
5
6
|
name: string | undefined;
|
|
@@ -43,5 +44,5 @@ export interface InitOptions {
|
|
|
43
44
|
export type PromptFn = (question: string, fallback?: string) => Promise<string>;
|
|
44
45
|
export declare const initCommand: Command;
|
|
45
46
|
/** Validate inputs and write a new manifest without changing an existing app implicitly. */
|
|
46
|
-
export declare function runInit(args: readonly string[], io: CliIo, prompt: PromptFn): Promise<ExitCode>;
|
|
47
|
+
export declare function runInit(args: readonly string[], io: CliIo, prompt: PromptFn, identity?: () => Promise<PrivateOwner>): Promise<ExitCode>;
|
|
47
48
|
//# sourceMappingURL=init.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AA4CA,OAAO,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACjE,OAAO,EAIL,KAAK,OAAO,EACZ,KAAK,QAAQ,EACd,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AA4CA,OAAO,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACjE,OAAO,EAIL,KAAK,OAAO,EACZ,KAAK,QAAQ,EACd,MAAM,uBAAuB,CAAC;AAI/B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAK7D,sDAAsD;AACtD,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,IAAI,EAAE,OAAO,GAAG,SAAS,CAAC;IAC1B;;;;OAIG;IACH,IAAI,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC3B,iFAAiF;IACjF,MAAM,EAAE,OAAO,GAAG,UAAU,GAAG,SAAS,CAAC;IACzC;;;;OAIG;IACH,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,eAAe,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,cAAc,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;CACnC;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B;;;;;OAKG;IACH,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;CAC9B;AAED,iEAAiE;AACjE,MAAM,MAAM,QAAQ,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;AAEhF,eAAO,MAAM,WAAW,EAAE,OAIzB,CAAC;AAEF,4FAA4F;AAC5F,wBAAsB,OAAO,CAC3B,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,EAAE,EAAE,KAAK,EACT,MAAM,EAAE,QAAQ,EAChB,QAAQ,GAAE,MAAM,OAAO,CAAC,YAAY,CAAuE,GAC1G,OAAO,CAAC,QAAQ,CAAC,CA8LnB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"served-verify.d.ts","sourceRoot":"","sources":["../../src/deploy/served-verify.ts"],"names":[],"mappings":"AAmBA,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,OAAO,GAAG,WAAW,GAAG,WAAW,CAAC;AAE3E,MAAM,MAAM,aAAa,GACrB;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;CAAE;AAC5B;;;;;;;;GAQG;GACD;IAAE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAA;CAAE,GAC9B;IACE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B,GACD;IAAE,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAExF,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,KAAK,EAAE,OAAO,KAAK,CAAC;CAC9B;AAED,MAAM,WAAW,UAAU;IACzB,wEAAwE;IACxE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,iEAAiE;IACjE,QAAQ,CAAC,iBAAiB,EAAE,UAAU,GAAG,MAAM,CAAC;IAChD,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;CACjC;AAUD;;;;GAIG;AACH,wBAAsB,YAAY,CAChC,IAAI,EAAE,UAAU,EAChB,IAAI,GAAE,UAAsB,GAC3B,OAAO,CAAC,aAAa,CAAC,
|
|
1
|
+
{"version":3,"file":"served-verify.d.ts","sourceRoot":"","sources":["../../src/deploy/served-verify.ts"],"names":[],"mappings":"AAmBA,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,OAAO,GAAG,WAAW,GAAG,WAAW,CAAC;AAE3E,MAAM,MAAM,aAAa,GACrB;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;CAAE;AAC5B;;;;;;;;GAQG;GACD;IAAE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAA;CAAE,GAC9B;IACE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B,GACD;IAAE,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAExF,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,KAAK,EAAE,OAAO,KAAK,CAAC;CAC9B;AAED,MAAM,WAAW,UAAU;IACzB,wEAAwE;IACxE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,iEAAiE;IACjE,QAAQ,CAAC,iBAAiB,EAAE,UAAU,GAAG,MAAM,CAAC;IAChD,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;CACjC;AAUD;;;;GAIG;AACH,wBAAsB,YAAY,CAChC,IAAI,EAAE,UAAU,EAChB,IAAI,GAAE,UAAsB,GAC3B,OAAO,CAAC,aAAa,CAAC,CAmDxB"}
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const CLI_VERSION = "0.
|
|
1
|
+
export declare const CLI_VERSION = "0.61.1";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: launchpad-content-pr
|
|
3
3
|
description: Push a content change to a Launchpad app via `launchpad deploy` and verify it shipped via `launchpad status`. Covers the post-first-deploy iteration loop (edit → deploy → verify) — subsequent deploys commit directly to the app repo's main and the Pages build runs asynchronously, so verification is its own step. Use when someone says "push a content change", "ship an update", "/launchpad-content-pr", "verify my deploy", or after `/launchpad-deploy` reports `done` and they want to follow up with an edit.
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.61.1
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<!-- BEGIN shell-contract (managed by scripts/sync-skill-contract.sh — edit skills/_partials/shell-contract.md) -->
|
|
@@ -51,6 +51,18 @@ This skill is therefore the **iteration** companion to
|
|
|
51
51
|
`/launchpad-deploy`: once an app is live, how do you ship the next
|
|
52
52
|
change — and how do you know it's actually serving.
|
|
53
53
|
|
|
54
|
+
## Audience changes
|
|
55
|
+
|
|
56
|
+
Keep the existing audience for ordinary content updates.
|
|
57
|
+
Do not broaden groups to bypass a validation or deployment failure.
|
|
58
|
+
If this update newly grants a company-wide audience, ask: **"Are you sure you want every employee to be able to open this app?"**
|
|
59
|
+
Name the app and groups, explain that any selected group grants access, and give the human the server's confirmation URL.
|
|
60
|
+
Never submit the browser decision on their behalf.
|
|
61
|
+
Wait for their confirmation, then retry the unchanged deployment.
|
|
62
|
+
Sensitive apps also require platform-admin approval.
|
|
63
|
+
Do not review or recertify unchanged existing audiences as part of a routine update.
|
|
64
|
+
Catalogue visibility controls discovery, not access; platform administrators retain entry independently of app groups.
|
|
65
|
+
|
|
54
66
|
## Pre-flight
|
|
55
67
|
|
|
56
68
|
```bash
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: launchpad-deploy
|
|
3
3
|
description: Walk a Launchpad user through deploying an app from their local working directory (Model A — `launchpad init` + `launchpad deploy`). Wraps the CLI verbs end-to-end: detects the app shape, scaffolds `launchpad.yaml`, resolves the allowed Entra group via `launchpad groups`, bundles the CWD via `launchpad deploy`, and watches the rollout via `launchpad status`. Use when someone says "deploy a new app", "ship my app to Launchpad", "/launchpad-deploy", "I have an app locally — get it on Launchpad", or any variant. Resume/abandon for legacy in-flight provisioning is at the bottom.
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.61.1
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<!-- BEGIN shell-contract (managed by scripts/sync-skill-contract.sh — edit skills/_partials/shell-contract.md) -->
|
|
@@ -39,6 +39,26 @@ required; no M-KOPA GitHub access required.** External users without
|
|
|
39
39
|
M-KOPA GitHub access are first-class — a Launchpad sign-in
|
|
40
40
|
(`launchpad login`, M-KOPA Microsoft account) is all they need.
|
|
41
41
|
|
|
42
|
+
## App audience and human confirmation
|
|
43
|
+
|
|
44
|
+
New apps are private and unlisted by default.
|
|
45
|
+
Only the verified creator and platform administrators, including Kevin Robertson, can open a private app.
|
|
46
|
+
Do not select a group unless the user deliberately wants to share the app with that audience.
|
|
47
|
+
Never select `S_ALL_Employees`, `S_ALL`, or another broad group to fix a missing-group error or make a deployment succeed.
|
|
48
|
+
|
|
49
|
+
Before requesting a new company-wide grant, ask the human exactly: **"Are you sure you want every employee to be able to open this app?"**
|
|
50
|
+
Name the app and the selected groups, and explain that membership in any selected group grants access.
|
|
51
|
+
A general instruction to deploy, a generic yes flag, and a previous approval for another app or revision do not answer this question.
|
|
52
|
+
When the server returns `audience_confirmation_required`, give the user its confirmation URL and wait for them to complete the browser decision.
|
|
53
|
+
Never open the decision endpoint with automation to approve it, submit its form, or manufacture an acknowledgement for the user.
|
|
54
|
+
Retry the same deployment only after the human confirms completion.
|
|
55
|
+
Sensitive apps also require a platform administrator's approval.
|
|
56
|
+
A changed audience or revision needs a new decision; unchanged existing audiences do not require recertification.
|
|
57
|
+
|
|
58
|
+
Catalogue listing controls discovery only.
|
|
59
|
+
It neither grants access nor makes an app private.
|
|
60
|
+
Platform-admin entry is independent of app groups, ownership and listing, and does not assign app-specific business roles.
|
|
61
|
+
|
|
42
62
|
## Constants (single source of truth)
|
|
43
63
|
|
|
44
64
|
These values are referenced by `/launchpad-deploy-status`,
|
|
@@ -147,25 +167,11 @@ This is interactive by default. It will ask for:
|
|
|
147
167
|
- **deployment type** — `static`, `react`, `react+api`, or
|
|
148
168
|
`container`. The detector pre-selects the right one for Vite/React
|
|
149
169
|
layouts; the user overrides if needed.
|
|
150
|
-
- **
|
|
151
|
-
|
|
152
|
-
By default the app is **listed in the app catalogue**
|
|
153
|
-
(get.launchpad/catalogue — the employee-facing app directory). Listing
|
|
154
|
-
is discovery only and grants no one access. To keep an internal-only
|
|
155
|
-
app out of it, add this under `spec` in a v2 manifest:
|
|
170
|
+
- **audience**: private unless the user deliberately selects groups; see §A.3.
|
|
156
171
|
|
|
157
|
-
|
|
158
|
-
spec:
|
|
159
|
-
|
|
160
|
-
listed: false
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
For a v1 Pages manifest, use top-level `catalogue` instead:
|
|
164
|
-
|
|
165
|
-
```yaml
|
|
166
|
-
catalogue:
|
|
167
|
-
listed: false
|
|
168
|
-
```
|
|
172
|
+
New apps are unlisted in the employee app catalogue.
|
|
173
|
+
To make a v2 app discoverable, set `spec.catalogue.listed: true`; v1 uses top-level `catalogue.listed`.
|
|
174
|
+
This changes discovery only and does not change who can open the app.
|
|
169
175
|
|
|
170
176
|
Non-interactive form for scripting:
|
|
171
177
|
|
|
@@ -174,12 +180,11 @@ launchpad init --non-interactive \
|
|
|
174
180
|
--name <slug> \
|
|
175
181
|
--type <apptype> \
|
|
176
182
|
--team <team> \
|
|
177
|
-
--owner <owner-email>
|
|
178
|
-
--group <group>
|
|
183
|
+
--owner <owner-email>
|
|
179
184
|
```
|
|
180
185
|
|
|
181
186
|
`--type` must be one of `static`, `react`, `react+api`, or
|
|
182
|
-
`container`. `--group` (preferred long form `--allowed-group`,
|
|
187
|
+
`container`. Omit `--group` for private access. `--group` (preferred long form `--allowed-group`,
|
|
183
188
|
repeatable for multiple groups) accepts the Entra group's display
|
|
184
189
|
name or UUID (the bot canonicalises to the UUID). Other flags:
|
|
185
190
|
`--description <text>`, `--auth <mode>` (see §A.5),
|
|
@@ -215,7 +220,7 @@ the existing app uses any of these, plan the swap **before** picking
|
|
|
215
220
|
a slug — do not deploy first and port later. What the bot *does*
|
|
216
221
|
enforce at deploy time is the gate set in §A.5.
|
|
217
222
|
|
|
218
|
-
### A.3
|
|
223
|
+
### A.3 Deliberately share with an Entra group
|
|
219
224
|
|
|
220
225
|
The CLI resolves Entra groups via the bot's `/groups` endpoint
|
|
221
226
|
(Entra-canonical, per M-940). The playbook never speaks to that
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: launchpad-deploy-status
|
|
3
3
|
description: Show the current provisioning stage + failure reason for a Launchpad app via `launchpad status` (Model A drift + deployment_verified) and `launchpad apps` (lifecycle bucket), or watch provisioning live with `launchpad watch`. Renders the M-892 stage trace for in-flight provisioning, and is the canonical home for `launchpad recover` (repair a terminal-failed app record that is actually live). Use when someone says "what's the status of demo-X", "/launchpad-deploy-status", "is my deploy stuck", "watch my deploy go live", "watch provisioning", "my app says failed but it's serving", or after `/launchpad-deploy` reports a non-`done` terminal stage.
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.61.1
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<!-- BEGIN shell-contract (managed by scripts/sync-skill-contract.sh — edit skills/_partials/shell-contract.md) -->
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: launchpad-destroy
|
|
3
3
|
description: Tear down a Launchpad app end-to-end via `launchpad destroy` — Cloudflare Pages project, edge-auth wiring (gateway KV/audience entries, or the Access app for `auth: access` apps), custom hostname, platform-repo TF, and the app repo (archive-renamed). Owner-only verb with a two-step destructive confirmation. Use when someone says "destroy this app", "/launchpad-destroy", "tear down `<slug>`", "delete the app", or asks to clean up a smoke-test / orphan / retired app.
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.61.1
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<!-- BEGIN shell-contract (managed by scripts/sync-skill-contract.sh — edit skills/_partials/shell-contract.md) -->
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: launchpad-identity
|
|
3
3
|
description: Teach an app author how to use the signed-in user's identity inside a Launchpad app — read the gateway-forwarded X-Launchpad-User-Assertion in a Pages Function, VERIFY it with @m-kopa/platform-auth (fail-closed), and show who's logged in (sub/email/name). Use when someone says "who is logged in", "show the current user", "get the user's email in my app", "auth in my launchpad app", "read the user identity", "/launchpad-identity", or is wiring up an /api/me for a gateway-fronted app.
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.61.1
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<!-- BEGIN shell-contract (managed by scripts/sync-skill-contract.sh — edit skills/_partials/shell-contract.md) -->
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: launchpad-onboard
|
|
3
3
|
description: One-time setup for the Launchpad CLI + Claude Code skill bundle. Verifies the `launchpad` CLI is installed and current, runs `launchpad whoami` to confirm the session is fresh, and checks the bundled skills are installed and in lock-step with the CLI. Idempotent — safe to re-run any time. Use when someone says "set me up for Launchpad", "I just got a new machine and want to use Launchpad", "/launchpad-onboard", or any of the other launchpad-* skills fails on a prereq check.
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.61.1
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<!-- BEGIN shell-contract (managed by scripts/sync-skill-contract.sh — edit skills/_partials/shell-contract.md) -->
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: launchpad-report
|
|
3
3
|
description: File a bug report or feature request to the Launchpad team's tracker from the CLI. Use when someone reports something broken, hits an error in a launchpad command, or wishes a feature existed — e.g. "this is broken", "report a bug", "can you file that", "I wish launchpad could…", "/launchpad-bug", "/launchpad-feature". Always confirm and show exactly what you'll send before filing; never file silently.
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.61.1
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<!-- BEGIN shell-contract (managed by scripts/sync-skill-contract.sh — edit skills/_partials/shell-contract.md) -->
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: launchpad-status
|
|
3
3
|
description: Safely refresh a Launchpad clone, show manifest drift, or read the deployed manifest. Wraps bare `launchpad pull` for source sync, `launchpad pull --manifest` for deployed YAML, and `launchpad status` for drift. Use for "sync my clone", "what's deployed", "show drift", "/launchpad-status", "/launchpad-pull", or post-deploy verification.
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.61.1
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<!-- BEGIN shell-contract (managed by scripts/sync-skill-contract.sh — edit skills/_partials/shell-contract.md) -->
|