@littlebearapps/platform-admin-sdk 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +112 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +89 -0
- package/dist/prompts.d.ts +27 -0
- package/dist/prompts.js +80 -0
- package/dist/scaffold.d.ts +5 -0
- package/dist/scaffold.js +65 -0
- package/dist/templates.d.ts +16 -0
- package/dist/templates.js +131 -0
- package/package.json +46 -0
- package/templates/full/migrations/006_pattern_discovery.sql +199 -0
- package/templates/full/migrations/007_notifications_search.sql +127 -0
- package/templates/full/workers/lib/pattern-discovery/ai-prompt.ts +644 -0
- package/templates/full/workers/lib/pattern-discovery/clustering.ts +278 -0
- package/templates/full/workers/lib/pattern-discovery/shadow-evaluation.ts +603 -0
- package/templates/full/workers/lib/pattern-discovery/storage.ts +806 -0
- package/templates/full/workers/lib/pattern-discovery/types.ts +159 -0
- package/templates/full/workers/lib/pattern-discovery/validation.ts +278 -0
- package/templates/full/workers/pattern-discovery.ts +661 -0
- package/templates/full/workers/platform-alert-router.ts +1809 -0
- package/templates/full/workers/platform-notifications.ts +424 -0
- package/templates/full/workers/platform-search.ts +480 -0
- package/templates/full/workers/platform-settings.ts +436 -0
- package/templates/full/wrangler.alert-router.jsonc.hbs +34 -0
- package/templates/full/wrangler.notifications.jsonc.hbs +23 -0
- package/templates/full/wrangler.pattern-discovery.jsonc.hbs +33 -0
- package/templates/full/wrangler.search.jsonc.hbs +16 -0
- package/templates/full/wrangler.settings.jsonc.hbs +23 -0
- package/templates/shared/README.md.hbs +69 -0
- package/templates/shared/config/budgets.yaml.hbs +72 -0
- package/templates/shared/config/services.yaml.hbs +45 -0
- package/templates/shared/migrations/001_core_tables.sql +117 -0
- package/templates/shared/migrations/002_usage_warehouse.sql +830 -0
- package/templates/shared/migrations/003_feature_tracking.sql +250 -0
- package/templates/shared/migrations/004_settings_alerts.sql +452 -0
- package/templates/shared/migrations/seed.sql.hbs +4 -0
- package/templates/shared/package.json.hbs +21 -0
- package/templates/shared/scripts/sync-config.ts +242 -0
- package/templates/shared/tsconfig.json +12 -0
- package/templates/shared/workers/lib/analytics-engine.ts +357 -0
- package/templates/shared/workers/lib/billing.ts +293 -0
- package/templates/shared/workers/lib/circuit-breaker-middleware.ts +25 -0
- package/templates/shared/workers/lib/control.ts +292 -0
- package/templates/shared/workers/lib/economics.ts +368 -0
- package/templates/shared/workers/lib/metrics.ts +103 -0
- package/templates/shared/workers/lib/platform-settings.ts +407 -0
- package/templates/shared/workers/lib/shared/allowances.ts +333 -0
- package/templates/shared/workers/lib/shared/cloudflare.ts +1362 -0
- package/templates/shared/workers/lib/shared/types.ts +58 -0
- package/templates/shared/workers/lib/telemetry-sampling.ts +360 -0
- package/templates/shared/workers/lib/usage/collectors/example.ts +96 -0
- package/templates/shared/workers/lib/usage/collectors/index.ts +128 -0
- package/templates/shared/workers/lib/usage/handlers/audit.ts +306 -0
- package/templates/shared/workers/lib/usage/handlers/backfill.ts +845 -0
- package/templates/shared/workers/lib/usage/handlers/behavioral.ts +429 -0
- package/templates/shared/workers/lib/usage/handlers/data-queries.ts +507 -0
- package/templates/shared/workers/lib/usage/handlers/dlq-admin.ts +364 -0
- package/templates/shared/workers/lib/usage/handlers/health-trends.ts +222 -0
- package/templates/shared/workers/lib/usage/handlers/index.ts +35 -0
- package/templates/shared/workers/lib/usage/handlers/usage-admin.ts +421 -0
- package/templates/shared/workers/lib/usage/handlers/usage-features.ts +1262 -0
- package/templates/shared/workers/lib/usage/handlers/usage-metrics.ts +2420 -0
- package/templates/shared/workers/lib/usage/handlers/usage-settings.ts +610 -0
- package/templates/shared/workers/lib/usage/queue/budget-enforcement.ts +1032 -0
- package/templates/shared/workers/lib/usage/queue/cost-budget-enforcement.ts +128 -0
- package/templates/shared/workers/lib/usage/queue/cost-calculator.ts +77 -0
- package/templates/shared/workers/lib/usage/queue/dlq-handler.ts +161 -0
- package/templates/shared/workers/lib/usage/queue/index.ts +19 -0
- package/templates/shared/workers/lib/usage/queue/telemetry-processor.ts +790 -0
- package/templates/shared/workers/lib/usage/scheduled/anomaly-detection.ts +732 -0
- package/templates/shared/workers/lib/usage/scheduled/data-collection.ts +956 -0
- package/templates/shared/workers/lib/usage/scheduled/error-digest.ts +343 -0
- package/templates/shared/workers/lib/usage/scheduled/index.ts +18 -0
- package/templates/shared/workers/lib/usage/scheduled/rollups.ts +1561 -0
- package/templates/shared/workers/lib/usage/shared/constants.ts +362 -0
- package/templates/shared/workers/lib/usage/shared/index.ts +14 -0
- package/templates/shared/workers/lib/usage/shared/types.ts +1066 -0
- package/templates/shared/workers/lib/usage/shared/utils.ts +795 -0
- package/templates/shared/workers/platform-usage.ts +1915 -0
- package/templates/shared/wrangler.usage.jsonc.hbs +58 -0
- package/templates/standard/migrations/005_error_collection.sql +162 -0
- package/templates/standard/workers/error-collector.ts +2670 -0
- package/templates/standard/workers/lib/error-collector/capture.ts +213 -0
- package/templates/standard/workers/lib/error-collector/digest.ts +448 -0
- package/templates/standard/workers/lib/error-collector/email-health-alerts.ts +262 -0
- package/templates/standard/workers/lib/error-collector/fingerprint.ts +258 -0
- package/templates/standard/workers/lib/error-collector/gap-alerts.ts +293 -0
- package/templates/standard/workers/lib/error-collector/github.ts +329 -0
- package/templates/standard/workers/lib/error-collector/types.ts +262 -0
- package/templates/standard/workers/lib/sentinel/gap-detection.ts +734 -0
- package/templates/standard/workers/lib/shared/slack-alerts.ts +585 -0
- package/templates/standard/workers/platform-sentinel.ts +1744 -0
- package/templates/standard/wrangler.error-collector.jsonc.hbs +44 -0
- package/templates/standard/wrangler.sentinel.jsonc.hbs +45 -0
package/README.md
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Platform Admin SDK
|
|
2
|
+
|
|
3
|
+
**`@littlebearapps/platform-admin-sdk`** — Scaffold backend infrastructure for Cloudflare Workers cost protection.
|
|
4
|
+
|
|
5
|
+
Generates workers, D1 migrations, and config files. Run once, then you own the code.
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx @littlebearapps/platform-admin-sdk my-platform
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The CLI prompts for:
|
|
14
|
+
- **Project name** and slug
|
|
15
|
+
- **GitHub organisation** (for error collection GitHub issues)
|
|
16
|
+
- **Tier** — how much infrastructure to generate
|
|
17
|
+
- **Gatus URL** (optional) — for heartbeat monitoring
|
|
18
|
+
- **Default assignee** — GitHub username for error issues
|
|
19
|
+
|
|
20
|
+
### CLI Flags (non-interactive)
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npx @littlebearapps/platform-admin-sdk my-platform --tier full --github-org myorg --skip-prompts
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
| Flag | Description |
|
|
27
|
+
|------|------------|
|
|
28
|
+
| `--tier <tier>` | Infrastructure tier (`minimal`, `standard`, `full`) |
|
|
29
|
+
| `--github-org <org>` | GitHub organisation for error issues |
|
|
30
|
+
| `--gatus-url <url>` | Gatus status page URL |
|
|
31
|
+
| `--default-assignee <user>` | Default GitHub issue assignee |
|
|
32
|
+
| `--skip-prompts` | Fail if required flags missing (for CI/automation) |
|
|
33
|
+
|
|
34
|
+
## Tiers
|
|
35
|
+
|
|
36
|
+
| Tier | Workers | What You Get | Est. Cost |
|
|
37
|
+
|------|---------|-------------|-----------|
|
|
38
|
+
| **Minimal** | 1 | Budget enforcement, circuit breakers, usage telemetry | ~$0/mo |
|
|
39
|
+
| **Standard** | 3 | + Error collector (auto GitHub issues), gap detection sentinel | ~$0/mo |
|
|
40
|
+
| **Full** | 8 | + AI pattern discovery, alert router, notifications, search, settings | ~$5/mo |
|
|
41
|
+
|
|
42
|
+
## What Gets Generated
|
|
43
|
+
|
|
44
|
+
### All tiers
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
my-platform/
|
|
48
|
+
+-- platform/config/
|
|
49
|
+
| +-- services.yaml # Project registry, feature definitions
|
|
50
|
+
| +-- budgets.yaml # Daily limits, circuit breaker thresholds
|
|
51
|
+
+-- storage/d1/migrations/ # D1 schema (4 core migrations + seed)
|
|
52
|
+
+-- workers/
|
|
53
|
+
| +-- platform-usage.ts # Data warehouse worker (cron + queue consumer)
|
|
54
|
+
| +-- lib/ # Shared libraries (billing, analytics, budgets)
|
|
55
|
+
+-- scripts/
|
|
56
|
+
| +-- sync-config.ts # Sync YAML config to D1/KV
|
|
57
|
+
+-- wrangler.*.jsonc # Worker configs with binding placeholders
|
|
58
|
+
+-- package.json
|
|
59
|
+
+-- tsconfig.json
|
|
60
|
+
+-- README.md
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Standard tier adds
|
|
64
|
+
|
|
65
|
+
- `workers/error-collector.ts` — Tail worker that creates GitHub issues from errors
|
|
66
|
+
- `workers/platform-sentinel.ts` — Gap detection, cost monitoring, alerts
|
|
67
|
+
- `workers/lib/error-collector/` — Fingerprinting, deduplication, digest
|
|
68
|
+
- `workers/lib/sentinel/` — Project gap detection
|
|
69
|
+
- `storage/d1/migrations/005_error_collection.sql`
|
|
70
|
+
|
|
71
|
+
### Full tier adds
|
|
72
|
+
|
|
73
|
+
- `workers/pattern-discovery.ts` — AI-assisted transient error pattern discovery
|
|
74
|
+
- `workers/platform-alert-router.ts` — Unified alert normalisation and routing
|
|
75
|
+
- `workers/platform-notifications.ts` — In-app notification API
|
|
76
|
+
- `workers/platform-search.ts` — Full-text search (FTS5)
|
|
77
|
+
- `workers/platform-settings.ts` — Settings management API
|
|
78
|
+
- `workers/lib/pattern-discovery/` — Clustering, AI prompts, validation, shadow eval
|
|
79
|
+
- `storage/d1/migrations/006_pattern_discovery.sql`
|
|
80
|
+
- `storage/d1/migrations/007_notifications_search.sql`
|
|
81
|
+
|
|
82
|
+
## Post-Scaffold Steps
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
cd my-platform
|
|
86
|
+
npm install
|
|
87
|
+
|
|
88
|
+
# Create Cloudflare resources
|
|
89
|
+
npx wrangler d1 create my-platform-metrics
|
|
90
|
+
npx wrangler kv namespace create PLATFORM_CACHE
|
|
91
|
+
npx wrangler queues create my-platform-telemetry
|
|
92
|
+
npx wrangler queues create my-platform-telemetry-dlq
|
|
93
|
+
|
|
94
|
+
# Update resource IDs in wrangler.*.jsonc, then:
|
|
95
|
+
npm run sync:config
|
|
96
|
+
npx wrangler d1 migrations apply my-platform-metrics --remote
|
|
97
|
+
npx wrangler deploy -c wrangler.my-platform-usage.jsonc
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Consumer SDK
|
|
101
|
+
|
|
102
|
+
The generated workers use `@littlebearapps/platform-consumer-sdk` — the Consumer SDK. Install it in your application workers:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
npm install @littlebearapps/platform-consumer-sdk
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
See the [Consumer SDK README](../consumer-sdk/README.md) for integration details.
|
|
109
|
+
|
|
110
|
+
## License
|
|
111
|
+
|
|
112
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @littlebearapps/platform-admin-sdk
|
|
4
|
+
*
|
|
5
|
+
* Scaffolds a Cloudflare Workers platform with SDK integration,
|
|
6
|
+
* circuit breakers, and cost protection.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* npx @littlebearapps/platform-admin-sdk [project-name] [options]
|
|
10
|
+
*
|
|
11
|
+
* Examples:
|
|
12
|
+
* npx @littlebearapps/platform-admin-sdk my-project
|
|
13
|
+
* npx @littlebearapps/platform-admin-sdk my-project --tier full --github-org myorg
|
|
14
|
+
* npx @littlebearapps/platform-admin-sdk my-project --tier minimal --skip-prompts
|
|
15
|
+
*/
|
|
16
|
+
export {};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @littlebearapps/platform-admin-sdk
|
|
4
|
+
*
|
|
5
|
+
* Scaffolds a Cloudflare Workers platform with SDK integration,
|
|
6
|
+
* circuit breakers, and cost protection.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* npx @littlebearapps/platform-admin-sdk [project-name] [options]
|
|
10
|
+
*
|
|
11
|
+
* Examples:
|
|
12
|
+
* npx @littlebearapps/platform-admin-sdk my-project
|
|
13
|
+
* npx @littlebearapps/platform-admin-sdk my-project --tier full --github-org myorg
|
|
14
|
+
* npx @littlebearapps/platform-admin-sdk my-project --tier minimal --skip-prompts
|
|
15
|
+
*/
|
|
16
|
+
import { resolve } from 'node:path';
|
|
17
|
+
import { Command } from 'commander';
|
|
18
|
+
import pc from 'picocolors';
|
|
19
|
+
import { collectOptions, isValidTier } from './prompts.js';
|
|
20
|
+
import { scaffold } from './scaffold.js';
|
|
21
|
+
const BANNER = `
|
|
22
|
+
${pc.bold(pc.cyan('Platform Admin SDK'))} — Cloudflare Cost Protection
|
|
23
|
+
${pc.dim('Scaffold backend infrastructure: circuit breakers, budget enforcement, error collection')}
|
|
24
|
+
`;
|
|
25
|
+
const program = new Command()
|
|
26
|
+
.name('platform-admin-sdk')
|
|
27
|
+
.description('Scaffold a Cloudflare Workers platform with SDK integration')
|
|
28
|
+
.version('1.0.0')
|
|
29
|
+
.argument('[project-name]', 'Name of the project to create')
|
|
30
|
+
.option('--tier <tier>', 'Infrastructure tier (minimal, standard, full)')
|
|
31
|
+
.option('--github-org <org>', 'GitHub organisation for error issue creation')
|
|
32
|
+
.option('--gatus-url <url>', 'Gatus status page URL for heartbeat monitoring')
|
|
33
|
+
.option('--default-assignee <user>', 'Default GitHub assignee for error issues')
|
|
34
|
+
.option('--skip-prompts', 'Non-interactive mode — fail if required flags are missing');
|
|
35
|
+
async function main() {
|
|
36
|
+
console.log(BANNER);
|
|
37
|
+
program.parse();
|
|
38
|
+
const opts = program.opts();
|
|
39
|
+
const [projectNameArg] = program.args;
|
|
40
|
+
// Validate tier if provided
|
|
41
|
+
if (opts.tier && !isValidTier(opts.tier)) {
|
|
42
|
+
console.error(pc.red(` Error: Invalid tier "${opts.tier}". Must be one of: minimal, standard, full`));
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
const options = await collectOptions({
|
|
46
|
+
projectName: projectNameArg,
|
|
47
|
+
tier: opts.tier,
|
|
48
|
+
githubOrg: opts.githubOrg,
|
|
49
|
+
gatusUrl: opts.gatusUrl,
|
|
50
|
+
defaultAssignee: opts.defaultAssignee,
|
|
51
|
+
skipPrompts: opts.skipPrompts,
|
|
52
|
+
});
|
|
53
|
+
const outputDir = resolve(process.cwd(), options.projectName);
|
|
54
|
+
console.log();
|
|
55
|
+
console.log(` ${pc.bold('Project')}: ${options.projectName}`);
|
|
56
|
+
console.log(` ${pc.bold('Tier')}: ${options.tier}`);
|
|
57
|
+
console.log(` ${pc.bold('Output')}: ${outputDir}`);
|
|
58
|
+
console.log();
|
|
59
|
+
await scaffold(options, outputDir);
|
|
60
|
+
console.log();
|
|
61
|
+
console.log(pc.green(pc.bold(' Done!')));
|
|
62
|
+
console.log();
|
|
63
|
+
console.log(` ${pc.bold('Next steps:')}`);
|
|
64
|
+
console.log();
|
|
65
|
+
console.log(` ${pc.cyan('cd')} ${options.projectName}`);
|
|
66
|
+
console.log(` ${pc.cyan('npm install')}`);
|
|
67
|
+
console.log();
|
|
68
|
+
console.log(` ${pc.dim('# Create Cloudflare resources:')}`);
|
|
69
|
+
console.log(` ${pc.cyan('npx wrangler d1 create')} ${options.projectSlug}-metrics`);
|
|
70
|
+
console.log(` ${pc.cyan('npx wrangler kv namespace create')} PLATFORM_CACHE`);
|
|
71
|
+
if (options.tier !== 'minimal') {
|
|
72
|
+
console.log(` ${pc.cyan('npx wrangler kv namespace create')} PLATFORM_ALERTS`);
|
|
73
|
+
}
|
|
74
|
+
console.log(` ${pc.cyan('npx wrangler queues create')} ${options.projectSlug}-telemetry`);
|
|
75
|
+
console.log(` ${pc.cyan('npx wrangler queues create')} ${options.projectSlug}-telemetry-dlq`);
|
|
76
|
+
console.log();
|
|
77
|
+
console.log(` ${pc.dim('# Update resource IDs in wrangler.*.jsonc, then:')}`);
|
|
78
|
+
console.log(` ${pc.cyan('npm run sync:config')}`);
|
|
79
|
+
console.log(` ${pc.cyan('npx wrangler d1 migrations apply')} ${options.projectSlug}-metrics --remote`);
|
|
80
|
+
console.log(` ${pc.cyan('npx wrangler deploy')} -c wrangler.${options.projectSlug}-usage.jsonc`);
|
|
81
|
+
console.log();
|
|
82
|
+
console.log(` ${pc.dim('# In your consumer projects:')}`);
|
|
83
|
+
console.log(` ${pc.cyan('npm install @littlebearapps/platform-consumer-sdk')}`);
|
|
84
|
+
console.log();
|
|
85
|
+
}
|
|
86
|
+
main().catch((error) => {
|
|
87
|
+
console.error(pc.red('Error:'), error instanceof Error ? error.message : String(error));
|
|
88
|
+
process.exit(1);
|
|
89
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive CLI prompts for project scaffolding configuration.
|
|
3
|
+
*
|
|
4
|
+
* When flags are provided via CLI, those values are used without prompting.
|
|
5
|
+
* Falls back to interactive prompts for missing values, or sensible defaults
|
|
6
|
+
* when running non-interactively.
|
|
7
|
+
*/
|
|
8
|
+
export type Tier = 'minimal' | 'standard' | 'full';
|
|
9
|
+
export interface ScaffoldOptions {
|
|
10
|
+
projectName: string;
|
|
11
|
+
projectSlug: string;
|
|
12
|
+
githubOrg: string;
|
|
13
|
+
tier: Tier;
|
|
14
|
+
gatusUrl: string;
|
|
15
|
+
defaultAssignee: string;
|
|
16
|
+
}
|
|
17
|
+
/** Pre-filled values from CLI flags. */
|
|
18
|
+
export interface CLIFlags {
|
|
19
|
+
projectName?: string;
|
|
20
|
+
tier?: Tier;
|
|
21
|
+
githubOrg?: string;
|
|
22
|
+
gatusUrl?: string;
|
|
23
|
+
defaultAssignee?: string;
|
|
24
|
+
skipPrompts?: boolean;
|
|
25
|
+
}
|
|
26
|
+
export declare function isValidTier(value: string): value is Tier;
|
|
27
|
+
export declare function collectOptions(flags?: CLIFlags): Promise<ScaffoldOptions>;
|
package/dist/prompts.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive CLI prompts for project scaffolding configuration.
|
|
3
|
+
*
|
|
4
|
+
* When flags are provided via CLI, those values are used without prompting.
|
|
5
|
+
* Falls back to interactive prompts for missing values, or sensible defaults
|
|
6
|
+
* when running non-interactively.
|
|
7
|
+
*/
|
|
8
|
+
import * as readline from 'node:readline';
|
|
9
|
+
const VALID_TIERS = ['minimal', 'standard', 'full'];
|
|
10
|
+
export function isValidTier(value) {
|
|
11
|
+
return VALID_TIERS.includes(value);
|
|
12
|
+
}
|
|
13
|
+
function slugify(name) {
|
|
14
|
+
return name
|
|
15
|
+
.toLowerCase()
|
|
16
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
17
|
+
.replace(/^-|-$/g, '');
|
|
18
|
+
}
|
|
19
|
+
async function prompt(question, defaultValue) {
|
|
20
|
+
// Non-interactive: use defaults
|
|
21
|
+
if (!process.stdin.isTTY) {
|
|
22
|
+
return defaultValue;
|
|
23
|
+
}
|
|
24
|
+
const rl = readline.createInterface({
|
|
25
|
+
input: process.stdin,
|
|
26
|
+
output: process.stdout,
|
|
27
|
+
});
|
|
28
|
+
return new Promise((resolve) => {
|
|
29
|
+
const display = defaultValue ? `${question} (${defaultValue}): ` : `${question}: `;
|
|
30
|
+
rl.question(` ${display}`, (answer) => {
|
|
31
|
+
rl.close();
|
|
32
|
+
resolve(answer.trim() || defaultValue);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
async function promptSelect(question, options, defaultIndex = 0) {
|
|
37
|
+
if (!process.stdin.isTTY) {
|
|
38
|
+
return options[defaultIndex];
|
|
39
|
+
}
|
|
40
|
+
console.log(` ${question}`);
|
|
41
|
+
options.forEach((opt, i) => {
|
|
42
|
+
const marker = i === defaultIndex ? '>' : ' ';
|
|
43
|
+
console.log(` ${marker} ${i + 1}. ${opt}`);
|
|
44
|
+
});
|
|
45
|
+
const answer = await prompt('Choose', String(defaultIndex + 1));
|
|
46
|
+
const idx = parseInt(answer, 10) - 1;
|
|
47
|
+
return options[idx] ?? options[defaultIndex];
|
|
48
|
+
}
|
|
49
|
+
export async function collectOptions(flags = {}) {
|
|
50
|
+
if (flags.skipPrompts) {
|
|
51
|
+
if (!flags.projectName) {
|
|
52
|
+
throw new Error('--skip-prompts requires a project name argument');
|
|
53
|
+
}
|
|
54
|
+
if (!flags.tier) {
|
|
55
|
+
throw new Error('--skip-prompts requires --tier');
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
projectName: flags.projectName,
|
|
59
|
+
projectSlug: slugify(flags.projectName),
|
|
60
|
+
githubOrg: flags.githubOrg ?? '',
|
|
61
|
+
tier: flags.tier,
|
|
62
|
+
gatusUrl: flags.gatusUrl ?? '',
|
|
63
|
+
defaultAssignee: flags.defaultAssignee ?? '',
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
const projectName = flags.projectName || await prompt('Project name', 'my-platform');
|
|
67
|
+
const projectSlug = await prompt('Project slug (for resource names)', slugify(projectName));
|
|
68
|
+
const tier = flags.tier || await promptSelect('Setup tier:', VALID_TIERS, 1);
|
|
69
|
+
const githubOrg = flags.githubOrg ?? await prompt('GitHub org (for error issue creation)', '');
|
|
70
|
+
const gatusUrl = flags.gatusUrl ?? await prompt('Gatus status page URL (optional)', '');
|
|
71
|
+
const defaultAssignee = flags.defaultAssignee ?? await prompt('Default GitHub assignee (optional)', '');
|
|
72
|
+
return {
|
|
73
|
+
projectName,
|
|
74
|
+
projectSlug,
|
|
75
|
+
githubOrg,
|
|
76
|
+
tier,
|
|
77
|
+
gatusUrl,
|
|
78
|
+
defaultAssignee,
|
|
79
|
+
};
|
|
80
|
+
}
|
package/dist/scaffold.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scaffolding orchestrator — copies and renders templates into the output directory.
|
|
3
|
+
*/
|
|
4
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
|
|
5
|
+
import { resolve, dirname, join } from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import Handlebars from 'handlebars';
|
|
8
|
+
import pc from 'picocolors';
|
|
9
|
+
import { getFilesForTier } from './templates.js';
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
+
const __dirname = dirname(__filename);
|
|
12
|
+
function getTemplatesDir() {
|
|
13
|
+
// In development: ../templates/
|
|
14
|
+
// In published package: ../templates/ (relative to dist/)
|
|
15
|
+
const devPath = resolve(__dirname, '..', 'templates');
|
|
16
|
+
if (existsSync(devPath))
|
|
17
|
+
return devPath;
|
|
18
|
+
return resolve(__dirname, '..', '..', 'templates');
|
|
19
|
+
}
|
|
20
|
+
function renderString(template, context) {
|
|
21
|
+
// Simple {{var}} replacement for file paths (not Handlebars — just string replace)
|
|
22
|
+
let result = template;
|
|
23
|
+
for (const [key, value] of Object.entries(context)) {
|
|
24
|
+
result = result.replace(new RegExp(`\\{\\{${key}\\}\\}`, 'g'), value);
|
|
25
|
+
}
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
export async function scaffold(options, outputDir) {
|
|
29
|
+
if (existsSync(outputDir)) {
|
|
30
|
+
throw new Error(`Directory already exists: ${outputDir}`);
|
|
31
|
+
}
|
|
32
|
+
const templatesDir = getTemplatesDir();
|
|
33
|
+
const files = getFilesForTier(options.tier);
|
|
34
|
+
const context = {
|
|
35
|
+
projectName: options.projectName,
|
|
36
|
+
projectSlug: options.projectSlug,
|
|
37
|
+
githubOrg: options.githubOrg,
|
|
38
|
+
tier: options.tier,
|
|
39
|
+
gatusUrl: options.gatusUrl,
|
|
40
|
+
defaultAssignee: options.defaultAssignee,
|
|
41
|
+
sdkVersion: '0.2.0',
|
|
42
|
+
};
|
|
43
|
+
mkdirSync(outputDir, { recursive: true });
|
|
44
|
+
for (const file of files) {
|
|
45
|
+
const srcPath = join(templatesDir, file.src);
|
|
46
|
+
const destPath = join(outputDir, renderString(file.dest, context));
|
|
47
|
+
// Ensure destination directory exists
|
|
48
|
+
mkdirSync(dirname(destPath), { recursive: true });
|
|
49
|
+
if (!existsSync(srcPath)) {
|
|
50
|
+
console.log(` ${pc.yellow('skip')} ${file.src} ${pc.dim('(template not found)')}`);
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
const raw = readFileSync(srcPath, 'utf-8');
|
|
54
|
+
if (file.template) {
|
|
55
|
+
const compiled = Handlebars.compile(raw, { noEscape: true });
|
|
56
|
+
const rendered = compiled(context);
|
|
57
|
+
writeFileSync(destPath, rendered);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
writeFileSync(destPath, raw);
|
|
61
|
+
}
|
|
62
|
+
const relDest = destPath.replace(outputDir + '/', '');
|
|
63
|
+
console.log(` ${pc.green('create')} ${relDest}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template manifest — maps tiers to files that should be scaffolded.
|
|
3
|
+
*
|
|
4
|
+
* Files ending in .hbs are rendered through Handlebars.
|
|
5
|
+
* All other files are copied verbatim.
|
|
6
|
+
*/
|
|
7
|
+
import type { Tier } from './prompts.js';
|
|
8
|
+
export interface TemplateFile {
|
|
9
|
+
/** Path relative to the templates/ directory */
|
|
10
|
+
src: string;
|
|
11
|
+
/** Path relative to the output directory */
|
|
12
|
+
dest: string;
|
|
13
|
+
/** Whether this file uses Handlebars templating */
|
|
14
|
+
template: boolean;
|
|
15
|
+
}
|
|
16
|
+
export declare function getFilesForTier(tier: Tier): TemplateFile[];
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template manifest — maps tiers to files that should be scaffolded.
|
|
3
|
+
*
|
|
4
|
+
* Files ending in .hbs are rendered through Handlebars.
|
|
5
|
+
* All other files are copied verbatim.
|
|
6
|
+
*/
|
|
7
|
+
const SHARED_FILES = [
|
|
8
|
+
// Config
|
|
9
|
+
{ src: 'shared/config/services.yaml.hbs', dest: 'platform/config/services.yaml', template: true },
|
|
10
|
+
{ src: 'shared/config/budgets.yaml.hbs', dest: 'platform/config/budgets.yaml', template: true },
|
|
11
|
+
// Scripts
|
|
12
|
+
{ src: 'shared/scripts/sync-config.ts', dest: 'scripts/sync-config.ts', template: false },
|
|
13
|
+
// Migrations (minimal tier)
|
|
14
|
+
{ src: 'shared/migrations/001_core_tables.sql', dest: 'storage/d1/migrations/001_core_tables.sql', template: false },
|
|
15
|
+
{ src: 'shared/migrations/002_usage_warehouse.sql', dest: 'storage/d1/migrations/002_usage_warehouse.sql', template: false },
|
|
16
|
+
{ src: 'shared/migrations/003_feature_tracking.sql', dest: 'storage/d1/migrations/003_feature_tracking.sql', template: false },
|
|
17
|
+
{ src: 'shared/migrations/004_settings_alerts.sql', dest: 'storage/d1/migrations/004_settings_alerts.sql', template: false },
|
|
18
|
+
{ src: 'shared/migrations/seed.sql.hbs', dest: 'storage/d1/migrations/seed.sql', template: true },
|
|
19
|
+
// Wrangler config (minimal)
|
|
20
|
+
{ src: 'shared/wrangler.usage.jsonc.hbs', dest: 'wrangler.{{projectSlug}}-usage.jsonc', template: true },
|
|
21
|
+
// Project files
|
|
22
|
+
{ src: 'shared/package.json.hbs', dest: 'package.json', template: true },
|
|
23
|
+
{ src: 'shared/tsconfig.json', dest: 'tsconfig.json', template: false },
|
|
24
|
+
{ src: 'shared/README.md.hbs', dest: 'README.md', template: true },
|
|
25
|
+
// Workers — platform-usage (data warehouse, cron + queue consumer)
|
|
26
|
+
{ src: 'shared/workers/platform-usage.ts', dest: 'workers/platform-usage.ts', template: false },
|
|
27
|
+
// Workers — root lib (shared utilities)
|
|
28
|
+
{ src: 'shared/workers/lib/billing.ts', dest: 'workers/lib/billing.ts', template: false },
|
|
29
|
+
{ src: 'shared/workers/lib/economics.ts', dest: 'workers/lib/economics.ts', template: false },
|
|
30
|
+
{ src: 'shared/workers/lib/analytics-engine.ts', dest: 'workers/lib/analytics-engine.ts', template: false },
|
|
31
|
+
{ src: 'shared/workers/lib/platform-settings.ts', dest: 'workers/lib/platform-settings.ts', template: false },
|
|
32
|
+
{ src: 'shared/workers/lib/circuit-breaker-middleware.ts', dest: 'workers/lib/circuit-breaker-middleware.ts', template: false },
|
|
33
|
+
{ src: 'shared/workers/lib/metrics.ts', dest: 'workers/lib/metrics.ts', template: false },
|
|
34
|
+
{ src: 'shared/workers/lib/telemetry-sampling.ts', dest: 'workers/lib/telemetry-sampling.ts', template: false },
|
|
35
|
+
{ src: 'shared/workers/lib/control.ts', dest: 'workers/lib/control.ts', template: false },
|
|
36
|
+
// Workers — lib/shared (cross-boundary types and utilities)
|
|
37
|
+
{ src: 'shared/workers/lib/shared/types.ts', dest: 'workers/lib/shared/types.ts', template: false },
|
|
38
|
+
{ src: 'shared/workers/lib/shared/allowances.ts', dest: 'workers/lib/shared/allowances.ts', template: false },
|
|
39
|
+
{ src: 'shared/workers/lib/shared/cloudflare.ts', dest: 'workers/lib/shared/cloudflare.ts', template: false },
|
|
40
|
+
// Workers — lib/usage/shared
|
|
41
|
+
{ src: 'shared/workers/lib/usage/shared/types.ts', dest: 'workers/lib/usage/shared/types.ts', template: false },
|
|
42
|
+
{ src: 'shared/workers/lib/usage/shared/constants.ts', dest: 'workers/lib/usage/shared/constants.ts', template: false },
|
|
43
|
+
{ src: 'shared/workers/lib/usage/shared/utils.ts', dest: 'workers/lib/usage/shared/utils.ts', template: false },
|
|
44
|
+
{ src: 'shared/workers/lib/usage/shared/index.ts', dest: 'workers/lib/usage/shared/index.ts', template: false },
|
|
45
|
+
// Workers — lib/usage/handlers
|
|
46
|
+
{ src: 'shared/workers/lib/usage/handlers/index.ts', dest: 'workers/lib/usage/handlers/index.ts', template: false },
|
|
47
|
+
{ src: 'shared/workers/lib/usage/handlers/data-queries.ts', dest: 'workers/lib/usage/handlers/data-queries.ts', template: false },
|
|
48
|
+
{ src: 'shared/workers/lib/usage/handlers/usage-metrics.ts', dest: 'workers/lib/usage/handlers/usage-metrics.ts', template: false },
|
|
49
|
+
{ src: 'shared/workers/lib/usage/handlers/usage-features.ts', dest: 'workers/lib/usage/handlers/usage-features.ts', template: false },
|
|
50
|
+
{ src: 'shared/workers/lib/usage/handlers/usage-settings.ts', dest: 'workers/lib/usage/handlers/usage-settings.ts', template: false },
|
|
51
|
+
{ src: 'shared/workers/lib/usage/handlers/usage-admin.ts', dest: 'workers/lib/usage/handlers/usage-admin.ts', template: false },
|
|
52
|
+
{ src: 'shared/workers/lib/usage/handlers/dlq-admin.ts', dest: 'workers/lib/usage/handlers/dlq-admin.ts', template: false },
|
|
53
|
+
{ src: 'shared/workers/lib/usage/handlers/health-trends.ts', dest: 'workers/lib/usage/handlers/health-trends.ts', template: false },
|
|
54
|
+
{ src: 'shared/workers/lib/usage/handlers/backfill.ts', dest: 'workers/lib/usage/handlers/backfill.ts', template: false },
|
|
55
|
+
{ src: 'shared/workers/lib/usage/handlers/audit.ts', dest: 'workers/lib/usage/handlers/audit.ts', template: false },
|
|
56
|
+
{ src: 'shared/workers/lib/usage/handlers/behavioral.ts', dest: 'workers/lib/usage/handlers/behavioral.ts', template: false },
|
|
57
|
+
// Workers — lib/usage/queue
|
|
58
|
+
{ src: 'shared/workers/lib/usage/queue/index.ts', dest: 'workers/lib/usage/queue/index.ts', template: false },
|
|
59
|
+
{ src: 'shared/workers/lib/usage/queue/telemetry-processor.ts', dest: 'workers/lib/usage/queue/telemetry-processor.ts', template: false },
|
|
60
|
+
{ src: 'shared/workers/lib/usage/queue/dlq-handler.ts', dest: 'workers/lib/usage/queue/dlq-handler.ts', template: false },
|
|
61
|
+
{ src: 'shared/workers/lib/usage/queue/budget-enforcement.ts', dest: 'workers/lib/usage/queue/budget-enforcement.ts', template: false },
|
|
62
|
+
{ src: 'shared/workers/lib/usage/queue/cost-calculator.ts', dest: 'workers/lib/usage/queue/cost-calculator.ts', template: false },
|
|
63
|
+
{ src: 'shared/workers/lib/usage/queue/cost-budget-enforcement.ts', dest: 'workers/lib/usage/queue/cost-budget-enforcement.ts', template: false },
|
|
64
|
+
// Workers — lib/usage/scheduled
|
|
65
|
+
{ src: 'shared/workers/lib/usage/scheduled/index.ts', dest: 'workers/lib/usage/scheduled/index.ts', template: false },
|
|
66
|
+
{ src: 'shared/workers/lib/usage/scheduled/data-collection.ts', dest: 'workers/lib/usage/scheduled/data-collection.ts', template: false },
|
|
67
|
+
{ src: 'shared/workers/lib/usage/scheduled/anomaly-detection.ts', dest: 'workers/lib/usage/scheduled/anomaly-detection.ts', template: false },
|
|
68
|
+
{ src: 'shared/workers/lib/usage/scheduled/rollups.ts', dest: 'workers/lib/usage/scheduled/rollups.ts', template: false },
|
|
69
|
+
{ src: 'shared/workers/lib/usage/scheduled/error-digest.ts', dest: 'workers/lib/usage/scheduled/error-digest.ts', template: false },
|
|
70
|
+
// Workers — lib/usage/collectors (pluggable interface + example)
|
|
71
|
+
{ src: 'shared/workers/lib/usage/collectors/index.ts', dest: 'workers/lib/usage/collectors/index.ts', template: false },
|
|
72
|
+
{ src: 'shared/workers/lib/usage/collectors/example.ts', dest: 'workers/lib/usage/collectors/example.ts', template: false },
|
|
73
|
+
];
|
|
74
|
+
const STANDARD_FILES = [
|
|
75
|
+
// Additional migrations
|
|
76
|
+
{ src: 'standard/migrations/005_error_collection.sql', dest: 'storage/d1/migrations/005_error_collection.sql', template: false },
|
|
77
|
+
// Wrangler configs
|
|
78
|
+
{ src: 'standard/wrangler.error-collector.jsonc.hbs', dest: 'wrangler.{{projectSlug}}-error-collector.jsonc', template: true },
|
|
79
|
+
{ src: 'standard/wrangler.sentinel.jsonc.hbs', dest: 'wrangler.{{projectSlug}}-sentinel.jsonc', template: true },
|
|
80
|
+
// Workers — error-collector (tail worker → GitHub issues)
|
|
81
|
+
{ src: 'standard/workers/error-collector.ts', dest: 'workers/error-collector.ts', template: false },
|
|
82
|
+
{ src: 'standard/workers/lib/error-collector/capture.ts', dest: 'workers/lib/error-collector/capture.ts', template: false },
|
|
83
|
+
{ src: 'standard/workers/lib/error-collector/fingerprint.ts', dest: 'workers/lib/error-collector/fingerprint.ts', template: false },
|
|
84
|
+
{ src: 'standard/workers/lib/error-collector/types.ts', dest: 'workers/lib/error-collector/types.ts', template: false },
|
|
85
|
+
{ src: 'standard/workers/lib/error-collector/github.ts', dest: 'workers/lib/error-collector/github.ts', template: false },
|
|
86
|
+
{ src: 'standard/workers/lib/error-collector/digest.ts', dest: 'workers/lib/error-collector/digest.ts', template: false },
|
|
87
|
+
{ src: 'standard/workers/lib/error-collector/gap-alerts.ts', dest: 'workers/lib/error-collector/gap-alerts.ts', template: false },
|
|
88
|
+
{ src: 'standard/workers/lib/error-collector/email-health-alerts.ts', dest: 'workers/lib/error-collector/email-health-alerts.ts', template: false },
|
|
89
|
+
// Workers — platform-sentinel (gap detection, cost monitoring)
|
|
90
|
+
{ src: 'standard/workers/platform-sentinel.ts', dest: 'workers/platform-sentinel.ts', template: false },
|
|
91
|
+
{ src: 'standard/workers/lib/sentinel/gap-detection.ts', dest: 'workers/lib/sentinel/gap-detection.ts', template: false },
|
|
92
|
+
// Workers — shared lib (used by standard-tier workers)
|
|
93
|
+
{ src: 'standard/workers/lib/shared/slack-alerts.ts', dest: 'workers/lib/shared/slack-alerts.ts', template: false },
|
|
94
|
+
];
|
|
95
|
+
const FULL_FILES = [
|
|
96
|
+
// Additional migrations
|
|
97
|
+
{ src: 'full/migrations/006_pattern_discovery.sql', dest: 'storage/d1/migrations/006_pattern_discovery.sql', template: false },
|
|
98
|
+
{ src: 'full/migrations/007_notifications_search.sql', dest: 'storage/d1/migrations/007_notifications_search.sql', template: false },
|
|
99
|
+
// Wrangler configs
|
|
100
|
+
{ src: 'full/wrangler.pattern-discovery.jsonc.hbs', dest: 'wrangler.{{projectSlug}}-pattern-discovery.jsonc', template: true },
|
|
101
|
+
{ src: 'full/wrangler.alert-router.jsonc.hbs', dest: 'wrangler.{{projectSlug}}-alert-router.jsonc', template: true },
|
|
102
|
+
{ src: 'full/wrangler.notifications.jsonc.hbs', dest: 'wrangler.{{projectSlug}}-notifications.jsonc', template: true },
|
|
103
|
+
{ src: 'full/wrangler.search.jsonc.hbs', dest: 'wrangler.{{projectSlug}}-search.jsonc', template: true },
|
|
104
|
+
{ src: 'full/wrangler.settings.jsonc.hbs', dest: 'wrangler.{{projectSlug}}-settings.jsonc', template: true },
|
|
105
|
+
// Workers — pattern-discovery (AI-assisted transient error patterns)
|
|
106
|
+
{ src: 'full/workers/pattern-discovery.ts', dest: 'workers/pattern-discovery.ts', template: false },
|
|
107
|
+
{ src: 'full/workers/lib/pattern-discovery/types.ts', dest: 'workers/lib/pattern-discovery/types.ts', template: false },
|
|
108
|
+
{ src: 'full/workers/lib/pattern-discovery/clustering.ts', dest: 'workers/lib/pattern-discovery/clustering.ts', template: false },
|
|
109
|
+
{ src: 'full/workers/lib/pattern-discovery/ai-prompt.ts', dest: 'workers/lib/pattern-discovery/ai-prompt.ts', template: false },
|
|
110
|
+
{ src: 'full/workers/lib/pattern-discovery/storage.ts', dest: 'workers/lib/pattern-discovery/storage.ts', template: false },
|
|
111
|
+
{ src: 'full/workers/lib/pattern-discovery/validation.ts', dest: 'workers/lib/pattern-discovery/validation.ts', template: false },
|
|
112
|
+
{ src: 'full/workers/lib/pattern-discovery/shadow-evaluation.ts', dest: 'workers/lib/pattern-discovery/shadow-evaluation.ts', template: false },
|
|
113
|
+
// Workers — platform-alert-router (unified alert normalisation)
|
|
114
|
+
{ src: 'full/workers/platform-alert-router.ts', dest: 'workers/platform-alert-router.ts', template: false },
|
|
115
|
+
// Workers — platform-notifications (in-app notification API)
|
|
116
|
+
{ src: 'full/workers/platform-notifications.ts', dest: 'workers/platform-notifications.ts', template: false },
|
|
117
|
+
// Workers — platform-search (full-text search FTS5)
|
|
118
|
+
{ src: 'full/workers/platform-search.ts', dest: 'workers/platform-search.ts', template: false },
|
|
119
|
+
// Workers — platform-settings (settings management API)
|
|
120
|
+
{ src: 'full/workers/platform-settings.ts', dest: 'workers/platform-settings.ts', template: false },
|
|
121
|
+
];
|
|
122
|
+
export function getFilesForTier(tier) {
|
|
123
|
+
const files = [...SHARED_FILES];
|
|
124
|
+
if (tier === 'standard' || tier === 'full') {
|
|
125
|
+
files.push(...STANDARD_FILES);
|
|
126
|
+
}
|
|
127
|
+
if (tier === 'full') {
|
|
128
|
+
files.push(...FULL_FILES);
|
|
129
|
+
}
|
|
130
|
+
return files;
|
|
131
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@littlebearapps/platform-admin-sdk",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Platform Admin SDK — scaffold backend infrastructure with workers, circuit breakers, and cost protection for Cloudflare",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"platform-admin-sdk": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist/",
|
|
11
|
+
"templates/"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc",
|
|
15
|
+
"test": "vitest run",
|
|
16
|
+
"typecheck": "tsc --noEmit",
|
|
17
|
+
"prepublishOnly": "npm run build"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"commander": "^14.0.3",
|
|
21
|
+
"handlebars": "^4.7.8",
|
|
22
|
+
"picocolors": "^1.1.1"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@types/node": "^22.0.0",
|
|
26
|
+
"typescript": "^5.7.3",
|
|
27
|
+
"vitest": "^3.0.5"
|
|
28
|
+
},
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "https://github.com/littlebearapps/platform-sdks.git",
|
|
35
|
+
"directory": "packages/admin-sdk"
|
|
36
|
+
},
|
|
37
|
+
"keywords": [
|
|
38
|
+
"cloudflare-workers",
|
|
39
|
+
"platform-admin-sdk",
|
|
40
|
+
"scaffold",
|
|
41
|
+
"cost-protection",
|
|
42
|
+
"circuit-breaker"
|
|
43
|
+
],
|
|
44
|
+
"author": "Little Bear Apps",
|
|
45
|
+
"license": "MIT"
|
|
46
|
+
}
|