@doubledigit/cli 0.1.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/LICENSE +21 -0
- package/dist/codegen.d.ts +12 -0
- package/dist/codegen.d.ts.map +1 -0
- package/dist/codegen.js +107 -0
- package/dist/commands/add.d.ts +26 -0
- package/dist/commands/add.d.ts.map +1 -0
- package/dist/commands/add.js +548 -0
- package/dist/commands/browse.d.ts +8 -0
- package/dist/commands/browse.d.ts.map +1 -0
- package/dist/commands/browse.js +116 -0
- package/dist/commands/create.d.ts +12 -0
- package/dist/commands/create.d.ts.map +1 -0
- package/dist/commands/create.js +218 -0
- package/dist/commands/db.d.ts +2 -0
- package/dist/commands/db.d.ts.map +1 -0
- package/dist/commands/db.js +64 -0
- package/dist/commands/disable.d.ts +5 -0
- package/dist/commands/disable.d.ts.map +1 -0
- package/dist/commands/disable.js +29 -0
- package/dist/commands/doctor.d.ts +2 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +88 -0
- package/dist/commands/enable.d.ts +5 -0
- package/dist/commands/enable.d.ts.map +1 -0
- package/dist/commands/enable.js +29 -0
- package/dist/commands/info.d.ts +8 -0
- package/dist/commands/info.d.ts.map +1 -0
- package/dist/commands/info.js +84 -0
- package/dist/commands/list.d.ts +5 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +44 -0
- package/dist/commands/marketplace.d.ts +11 -0
- package/dist/commands/marketplace.d.ts.map +1 -0
- package/dist/commands/marketplace.js +205 -0
- package/dist/commands/onboard.d.ts +2 -0
- package/dist/commands/onboard.d.ts.map +1 -0
- package/dist/commands/onboard.js +58 -0
- package/dist/commands/outdated.d.ts +8 -0
- package/dist/commands/outdated.d.ts.map +1 -0
- package/dist/commands/outdated.js +107 -0
- package/dist/commands/reconcile.d.ts +12 -0
- package/dist/commands/reconcile.d.ts.map +1 -0
- package/dist/commands/reconcile.js +175 -0
- package/dist/commands/run.d.ts +2 -0
- package/dist/commands/run.d.ts.map +1 -0
- package/dist/commands/run.js +37 -0
- package/dist/commands/sync.d.ts +5 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +34 -0
- package/dist/commands/uninstall.d.ts +14 -0
- package/dist/commands/uninstall.d.ts.map +1 -0
- package/dist/commands/uninstall.js +190 -0
- package/dist/config.d.ts +19 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +37 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +181 -0
- package/dist/lib/github-auth.d.ts +8 -0
- package/dist/lib/github-auth.d.ts.map +1 -0
- package/dist/lib/github-auth.js +30 -0
- package/dist/lib/lock-file.d.ts +67 -0
- package/dist/lib/lock-file.d.ts.map +1 -0
- package/dist/lib/lock-file.js +117 -0
- package/dist/lib/marketplace-schema.d.ts +607 -0
- package/dist/lib/marketplace-schema.d.ts.map +1 -0
- package/dist/lib/marketplace-schema.js +111 -0
- package/dist/lib/marketplace.d.ts +57 -0
- package/dist/lib/marketplace.d.ts.map +1 -0
- package/dist/lib/marketplace.js +270 -0
- package/dist/lib/onboarding.d.ts +84 -0
- package/dist/lib/onboarding.d.ts.map +1 -0
- package/dist/lib/onboarding.js +1004 -0
- package/dist/lib/rewrite-extension-tsconfig.d.ts +22 -0
- package/dist/lib/rewrite-extension-tsconfig.d.ts.map +1 -0
- package/dist/lib/rewrite-extension-tsconfig.js +80 -0
- package/dist/lib/source-parser.d.ts +35 -0
- package/dist/lib/source-parser.d.ts.map +1 -0
- package/dist/lib/source-parser.js +121 -0
- package/dist/lib/validators.d.ts +73 -0
- package/dist/lib/validators.d.ts.map +1 -0
- package/dist/lib/validators.js +435 -0
- package/dist/paths.d.ts +46 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +85 -0
- package/dist/scanner.d.ts +41 -0
- package/dist/scanner.d.ts.map +1 -0
- package/dist/scanner.js +100 -0
- package/package.json +49 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zod schemas for marketplace manifests and known-marketplaces config.
|
|
3
|
+
*
|
|
4
|
+
* Two files:
|
|
5
|
+
* 1. marketplace.json — lives inside a marketplace repo at .doubledigit/marketplace.json
|
|
6
|
+
* 2. marketplaces.json — lives in the consuming project at .doubledigit/marketplaces.json
|
|
7
|
+
*/
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// Safe marketplace name (used as filesystem path segment + JSON key)
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
const MARKETPLACE_NAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9_-]*$/;
|
|
13
|
+
export const MarketplaceNameSchema = z
|
|
14
|
+
.string()
|
|
15
|
+
.min(1)
|
|
16
|
+
.regex(MARKETPLACE_NAME_RE, 'Marketplace name must start with an alphanumeric character and contain only [a-zA-Z0-9_-]');
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Extension source (where the code lives)
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
export const ExtensionSourceSchema = z.object({
|
|
21
|
+
/** Source type — currently only git-subdir is supported */
|
|
22
|
+
type: z.enum(['git-subdir', 'git-repo']).default('git-subdir'),
|
|
23
|
+
/** GitHub owner/repo (e.g. "digitaldouble/micro-apps") */
|
|
24
|
+
url: z.string().min(1),
|
|
25
|
+
/** Path within the repo (e.g. "packages/habit-tracker") */
|
|
26
|
+
path: z.string().optional(),
|
|
27
|
+
/** Git ref (branch, tag, SHA) */
|
|
28
|
+
ref: z.string().optional(),
|
|
29
|
+
/** Pinned commit SHA for reproducibility */
|
|
30
|
+
sha: z.string().optional(),
|
|
31
|
+
});
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// Single extension entry in a marketplace manifest
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
export const ExtensionKindSchema = z.enum(['micro-app', 'payload-plugin']);
|
|
36
|
+
export const MarketplaceExtensionSchema = z.object({
|
|
37
|
+
/** Unique name within the marketplace */
|
|
38
|
+
name: z.string().min(1),
|
|
39
|
+
/** Extension kind */
|
|
40
|
+
kind: ExtensionKindSchema,
|
|
41
|
+
/** Human-readable description */
|
|
42
|
+
description: z.string().optional(),
|
|
43
|
+
/** Semver version string */
|
|
44
|
+
version: z.string().optional(),
|
|
45
|
+
/** Where the code lives */
|
|
46
|
+
source: ExtensionSourceSchema,
|
|
47
|
+
/** Whether marketplace or source is authoritative */
|
|
48
|
+
sourceAuthority: z.enum(['source', 'marketplace']).default('source'),
|
|
49
|
+
/** Verified by the marketplace maintainer */
|
|
50
|
+
verified: z.boolean().default(false),
|
|
51
|
+
/** Discovery tags */
|
|
52
|
+
tags: z.array(z.string()).default([]),
|
|
53
|
+
/** Author name or GitHub handle */
|
|
54
|
+
author: z.string().optional(),
|
|
55
|
+
/** Minimum platform version required */
|
|
56
|
+
minPlatformVersion: z.string().optional(),
|
|
57
|
+
});
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
// Marketplace manifest (.doubledigit/marketplace.json in a marketplace repo)
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
export const MarketplaceManifestSchema = z.object({
|
|
62
|
+
/** Marketplace display name (safe slug for filesystem use) */
|
|
63
|
+
name: MarketplaceNameSchema,
|
|
64
|
+
/** Marketplace owner info */
|
|
65
|
+
owner: z
|
|
66
|
+
.object({
|
|
67
|
+
name: z.string().min(1),
|
|
68
|
+
email: z.string().optional(),
|
|
69
|
+
url: z.string().optional(),
|
|
70
|
+
})
|
|
71
|
+
.optional(),
|
|
72
|
+
/** Marketplace metadata */
|
|
73
|
+
metadata: z
|
|
74
|
+
.object({
|
|
75
|
+
description: z.string().optional(),
|
|
76
|
+
version: z.string().optional(),
|
|
77
|
+
/** Root directory within the marketplace repo for extension dirs */
|
|
78
|
+
extensionRoot: z.string().optional(),
|
|
79
|
+
})
|
|
80
|
+
.optional(),
|
|
81
|
+
/** List of extensions in this marketplace */
|
|
82
|
+
extensions: z.array(MarketplaceExtensionSchema),
|
|
83
|
+
});
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
// Known marketplaces file (.doubledigit/marketplaces.json in the project)
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
export const KnownMarketplaceEntrySchema = z.object({
|
|
88
|
+
/** GitHub source (owner/repo or full URL) */
|
|
89
|
+
source: z.string().min(1),
|
|
90
|
+
/** Path within the repo to .doubledigit/marketplace.json (defaults to root) */
|
|
91
|
+
manifestPath: z.string().default('.doubledigit/marketplace.json'),
|
|
92
|
+
/** Git ref to fetch from */
|
|
93
|
+
ref: z.string().optional(),
|
|
94
|
+
/** Last-fetched manifest cache */
|
|
95
|
+
cachedAt: z.string().optional(),
|
|
96
|
+
/** Number of extensions in the last fetch */
|
|
97
|
+
extensionCount: z.number().optional(),
|
|
98
|
+
});
|
|
99
|
+
export const KnownMarketplacesFileSchema = z.object({
|
|
100
|
+
version: z.literal(1).default(1),
|
|
101
|
+
marketplaces: z.record(MarketplaceNameSchema, KnownMarketplaceEntrySchema),
|
|
102
|
+
});
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
// Marketplace catalog cache (.doubledigit/cache/<name>.json in the project)
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
export const CachedMarketplaceSchema = z.object({
|
|
107
|
+
/** When this cache was last updated */
|
|
108
|
+
fetchedAt: z.string(),
|
|
109
|
+
/** The marketplace manifest */
|
|
110
|
+
manifest: MarketplaceManifestSchema,
|
|
111
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Marketplace manager — fetch, cache, and query marketplace catalogs.
|
|
3
|
+
*
|
|
4
|
+
* Marketplaces are GitHub repos containing .doubledigit/marketplace.json.
|
|
5
|
+
* The consuming project tracks known marketplaces in .doubledigit/marketplaces.json
|
|
6
|
+
* and caches fetched catalogs in .doubledigit/cache/<name>.json.
|
|
7
|
+
*/
|
|
8
|
+
import { type KnownMarketplacesFile, type KnownMarketplaceEntry, type MarketplaceManifest, type CachedMarketplace, type MarketplaceExtension } from './marketplace-schema.js';
|
|
9
|
+
/**
|
|
10
|
+
* Validate a marketplace name is a safe slug for filesystem and JSON key use.
|
|
11
|
+
* Throws a descriptive error if the name is invalid. Returns the name for chaining.
|
|
12
|
+
*/
|
|
13
|
+
export declare function assertValidMarketplaceName(name: string): string;
|
|
14
|
+
export declare function getMarketplacesDir(root: string): string;
|
|
15
|
+
export declare function getMarketplacesFilePath(root: string): string;
|
|
16
|
+
export declare function getCacheDir(root: string): string;
|
|
17
|
+
export declare function getCachePath(root: string, name: string): string;
|
|
18
|
+
export declare function readKnownMarketplaces(root: string): KnownMarketplacesFile;
|
|
19
|
+
export declare function writeKnownMarketplaces(root: string, data: KnownMarketplacesFile): void;
|
|
20
|
+
export declare function addKnownMarketplace(root: string, name: string, entry: KnownMarketplaceEntry): void;
|
|
21
|
+
export declare function removeKnownMarketplace(root: string, name: string): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Fetch a marketplace manifest from a GitHub repo.
|
|
24
|
+
* Tries the GitHub raw content URL first.
|
|
25
|
+
*/
|
|
26
|
+
export declare function fetchMarketplaceManifest(source: string, manifestPath?: string, ref?: string): Promise<MarketplaceManifest>;
|
|
27
|
+
export declare function readCachedMarketplace(root: string, name: string): CachedMarketplace | null;
|
|
28
|
+
export declare function writeCachedMarketplace(root: string, name: string, manifest: MarketplaceManifest): void;
|
|
29
|
+
export declare function updateMarketplace(root: string, name: string): Promise<MarketplaceManifest>;
|
|
30
|
+
/**
|
|
31
|
+
* Get all extensions across all cached marketplaces.
|
|
32
|
+
*/
|
|
33
|
+
export declare function getAllCachedExtensions(root: string): Array<MarketplaceExtension & {
|
|
34
|
+
marketplace: string;
|
|
35
|
+
}>;
|
|
36
|
+
/**
|
|
37
|
+
* Resolve an extension name from a specific marketplace.
|
|
38
|
+
* Returns null if not found.
|
|
39
|
+
*/
|
|
40
|
+
export declare function resolveExtensionFromMarketplace(root: string, extensionName: string, marketplaceName: string): (MarketplaceExtension & {
|
|
41
|
+
marketplace: string;
|
|
42
|
+
}) | null;
|
|
43
|
+
/**
|
|
44
|
+
* Resolve an extension name across all known marketplaces.
|
|
45
|
+
* Returns first match (priority: verified first, then alphabetical marketplace order).
|
|
46
|
+
*/
|
|
47
|
+
export declare function resolveExtension(root: string, extensionName: string): (MarketplaceExtension & {
|
|
48
|
+
marketplace: string;
|
|
49
|
+
}) | null;
|
|
50
|
+
/**
|
|
51
|
+
* Convert a marketplace extension source to a `gh:owner/repo/path#ref` string
|
|
52
|
+
* compatible with the existing `dd add` pipeline.
|
|
53
|
+
*
|
|
54
|
+
* Prefers the pinned SHA over ref for reproducibility.
|
|
55
|
+
*/
|
|
56
|
+
export declare function extensionSourceToGhString(ext: MarketplaceExtension): string;
|
|
57
|
+
//# sourceMappingURL=marketplace.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"marketplace.d.ts","sourceRoot":"","sources":["../../src/lib/marketplace.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,EAKL,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EAC1B,MAAM,yBAAyB,CAAC;AAOjC;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAS/D;AAMD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE5D;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEhD;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAG/D;AAMD,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,qBAAqB,CA8BzE;AAED,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,qBAAqB,GAC1B,IAAI,CAkBN;AAED,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,qBAAqB,GAC3B,IAAI,CAKN;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAe1E;AAMD;;;GAGG;AACH,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,MAAM,EACd,YAAY,SAAkC,EAC9C,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC,mBAAmB,CAAC,CAqE9B;AAMD,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,iBAAiB,GAAG,IAAI,CAW1B;AAED,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,mBAAmB,GAC5B,IAAI,CAeN;AAMD,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,mBAAmB,CAAC,CAsB9B;AAMD;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,MAAM,GACX,KAAK,CAAC,oBAAoB,GAAG;IAAE,WAAW,EAAE,MAAM,CAAA;CAAE,CAAC,CAcvD;AAED;;;GAGG;AACH,wBAAgB,+BAA+B,CAC7C,IAAI,EAAE,MAAM,EACZ,aAAa,EAAE,MAAM,EACrB,eAAe,EAAE,MAAM,GACtB,CAAC,oBAAoB,GAAG;IAAE,WAAW,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,IAAI,CAUzD;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,aAAa,EAAE,MAAM,GACpB,CAAC,oBAAoB,GAAG;IAAE,WAAW,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,IAAI,CAYzD;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACvC,GAAG,EAAE,oBAAoB,GACxB,MAAM,CAOR"}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Marketplace manager — fetch, cache, and query marketplace catalogs.
|
|
3
|
+
*
|
|
4
|
+
* Marketplaces are GitHub repos containing .doubledigit/marketplace.json.
|
|
5
|
+
* The consuming project tracks known marketplaces in .doubledigit/marketplaces.json
|
|
6
|
+
* and caches fetched catalogs in .doubledigit/cache/<name>.json.
|
|
7
|
+
*/
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import { KnownMarketplacesFileSchema, MarketplaceManifestSchema, CachedMarketplaceSchema, MarketplaceNameSchema, } from './marketplace-schema.js';
|
|
11
|
+
import { resolveGitHubToken } from './github-auth.js';
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Name validation helper
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
/**
|
|
16
|
+
* Validate a marketplace name is a safe slug for filesystem and JSON key use.
|
|
17
|
+
* Throws a descriptive error if the name is invalid. Returns the name for chaining.
|
|
18
|
+
*/
|
|
19
|
+
export function assertValidMarketplaceName(name) {
|
|
20
|
+
const result = MarketplaceNameSchema.safeParse(name);
|
|
21
|
+
if (!result.success) {
|
|
22
|
+
throw new Error(`Invalid marketplace name: "${name}"\n` +
|
|
23
|
+
'Marketplace names must start with an alphanumeric character and contain only letters, digits, hyphens, and underscores.');
|
|
24
|
+
}
|
|
25
|
+
return name;
|
|
26
|
+
}
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// Paths
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
export function getMarketplacesDir(root) {
|
|
31
|
+
return path.join(root, '.doubledigit');
|
|
32
|
+
}
|
|
33
|
+
export function getMarketplacesFilePath(root) {
|
|
34
|
+
return path.join(getMarketplacesDir(root), 'marketplaces.json');
|
|
35
|
+
}
|
|
36
|
+
export function getCacheDir(root) {
|
|
37
|
+
return path.join(getMarketplacesDir(root), 'cache');
|
|
38
|
+
}
|
|
39
|
+
export function getCachePath(root, name) {
|
|
40
|
+
assertValidMarketplaceName(name);
|
|
41
|
+
return path.join(getCacheDir(root), `${name}.json`);
|
|
42
|
+
}
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
// Known marketplaces CRUD
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
export function readKnownMarketplaces(root) {
|
|
47
|
+
const filePath = getMarketplacesFilePath(root);
|
|
48
|
+
if (!fs.existsSync(filePath)) {
|
|
49
|
+
return { version: 1, marketplaces: {} };
|
|
50
|
+
}
|
|
51
|
+
let raw;
|
|
52
|
+
try {
|
|
53
|
+
raw = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
const backupPath = filePath + '.bak';
|
|
57
|
+
fs.copyFileSync(filePath, backupPath);
|
|
58
|
+
throw new Error(`Marketplaces file is corrupted (backed up to ${backupPath}).\n` +
|
|
59
|
+
`Fix or delete ${filePath} and retry.\n` +
|
|
60
|
+
`Parse error: ${err instanceof Error ? err.message : err}`);
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
return KnownMarketplacesFileSchema.parse(raw);
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
const backupPath = filePath + '.bak';
|
|
67
|
+
fs.copyFileSync(filePath, backupPath);
|
|
68
|
+
throw new Error(`Marketplaces file has invalid schema (backed up to ${backupPath}).\n` +
|
|
69
|
+
`Fix or delete ${filePath} and retry.\n` +
|
|
70
|
+
`Validation error: ${err instanceof Error ? err.message : err}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
export function writeKnownMarketplaces(root, data) {
|
|
74
|
+
const dir = getMarketplacesDir(root);
|
|
75
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
76
|
+
const sorted = {
|
|
77
|
+
version: data.version,
|
|
78
|
+
marketplaces: Object.fromEntries(Object.entries(data.marketplaces).sort(([a], [b]) => a.localeCompare(b))),
|
|
79
|
+
};
|
|
80
|
+
fs.writeFileSync(getMarketplacesFilePath(root), JSON.stringify(sorted, null, 2) + '\n', 'utf-8');
|
|
81
|
+
}
|
|
82
|
+
export function addKnownMarketplace(root, name, entry) {
|
|
83
|
+
assertValidMarketplaceName(name);
|
|
84
|
+
const data = readKnownMarketplaces(root);
|
|
85
|
+
data.marketplaces[name] = entry;
|
|
86
|
+
writeKnownMarketplaces(root, data);
|
|
87
|
+
}
|
|
88
|
+
export function removeKnownMarketplace(root, name) {
|
|
89
|
+
assertValidMarketplaceName(name);
|
|
90
|
+
const data = readKnownMarketplaces(root);
|
|
91
|
+
if (!(name in data.marketplaces))
|
|
92
|
+
return false;
|
|
93
|
+
delete data.marketplaces[name];
|
|
94
|
+
writeKnownMarketplaces(root, data);
|
|
95
|
+
// Also remove cached catalog
|
|
96
|
+
const cachePath = getCachePath(root, name);
|
|
97
|
+
if (fs.existsSync(cachePath)) {
|
|
98
|
+
fs.rmSync(cachePath, { force: true });
|
|
99
|
+
}
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
// Fetch marketplace manifest from GitHub
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
/**
|
|
106
|
+
* Fetch a marketplace manifest from a GitHub repo.
|
|
107
|
+
* Tries the GitHub raw content URL first.
|
|
108
|
+
*/
|
|
109
|
+
export async function fetchMarketplaceManifest(source, manifestPath = '.doubledigit/marketplace.json', ref) {
|
|
110
|
+
const token = resolveGitHubToken();
|
|
111
|
+
const headers = {
|
|
112
|
+
Accept: 'application/vnd.github.v3.raw',
|
|
113
|
+
'User-Agent': 'dd-cli',
|
|
114
|
+
};
|
|
115
|
+
if (token) {
|
|
116
|
+
headers['Authorization'] = `Bearer ${token}`;
|
|
117
|
+
}
|
|
118
|
+
// Parse source: "owner/repo" or full GitHub URL
|
|
119
|
+
let owner;
|
|
120
|
+
let repo;
|
|
121
|
+
let sourceRef;
|
|
122
|
+
if (source.includes('github.com')) {
|
|
123
|
+
const match = source.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\/tree\/(.+?))?\/?$/);
|
|
124
|
+
if (!match) {
|
|
125
|
+
throw new Error(`Invalid GitHub URL: ${source}\n` +
|
|
126
|
+
'Expected format: https://github.com/owner/repo or https://github.com/owner/repo/tree/ref');
|
|
127
|
+
}
|
|
128
|
+
owner = match[1];
|
|
129
|
+
repo = match[2].replace(/\.git$/, '');
|
|
130
|
+
sourceRef = match[3];
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
const parts = source.split('/');
|
|
134
|
+
if (parts.length < 2) {
|
|
135
|
+
throw new Error(`Invalid marketplace source: "${source}"\n` +
|
|
136
|
+
'Expected format: owner/repo, https://github.com/owner/repo, or https://github.com/owner/repo/tree/ref');
|
|
137
|
+
}
|
|
138
|
+
owner = parts[0];
|
|
139
|
+
repo = parts[1];
|
|
140
|
+
}
|
|
141
|
+
const gitRef = ref || sourceRef || 'main';
|
|
142
|
+
const url = `https://raw.githubusercontent.com/${owner}/${repo}/${gitRef}/${manifestPath}`;
|
|
143
|
+
const res = await fetch(url, { headers });
|
|
144
|
+
if (res.status === 404) {
|
|
145
|
+
// Try 'master' branch as fallback
|
|
146
|
+
if (!ref && !sourceRef) {
|
|
147
|
+
const fallbackUrl = `https://raw.githubusercontent.com/${owner}/${repo}/master/${manifestPath}`;
|
|
148
|
+
const fallbackRes = await fetch(fallbackUrl, { headers });
|
|
149
|
+
if (fallbackRes.ok) {
|
|
150
|
+
const data = await fallbackRes.json();
|
|
151
|
+
return MarketplaceManifestSchema.parse(data);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
throw new Error(`Marketplace manifest not found at ${owner}/${repo}/${manifestPath}\n` +
|
|
155
|
+
'Ensure the repo has a .doubledigit/marketplace.json file.');
|
|
156
|
+
}
|
|
157
|
+
if (!res.ok) {
|
|
158
|
+
throw new Error(`Failed to fetch marketplace manifest (${res.status}): ${url}`);
|
|
159
|
+
}
|
|
160
|
+
const data = await res.json();
|
|
161
|
+
return MarketplaceManifestSchema.parse(data);
|
|
162
|
+
}
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
// Cache management
|
|
165
|
+
// ---------------------------------------------------------------------------
|
|
166
|
+
export function readCachedMarketplace(root, name) {
|
|
167
|
+
assertValidMarketplaceName(name);
|
|
168
|
+
const cachePath = getCachePath(root, name);
|
|
169
|
+
if (!fs.existsSync(cachePath))
|
|
170
|
+
return null;
|
|
171
|
+
try {
|
|
172
|
+
const raw = JSON.parse(fs.readFileSync(cachePath, 'utf-8'));
|
|
173
|
+
return CachedMarketplaceSchema.parse(raw);
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
export function writeCachedMarketplace(root, name, manifest) {
|
|
180
|
+
assertValidMarketplaceName(name);
|
|
181
|
+
const cacheDir = getCacheDir(root);
|
|
182
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
183
|
+
const cached = {
|
|
184
|
+
fetchedAt: new Date().toISOString(),
|
|
185
|
+
manifest,
|
|
186
|
+
};
|
|
187
|
+
fs.writeFileSync(getCachePath(root, name), JSON.stringify(cached, null, 2) + '\n', 'utf-8');
|
|
188
|
+
}
|
|
189
|
+
// ---------------------------------------------------------------------------
|
|
190
|
+
// Update a single marketplace (fetch + cache + update known file)
|
|
191
|
+
// ---------------------------------------------------------------------------
|
|
192
|
+
export async function updateMarketplace(root, name) {
|
|
193
|
+
assertValidMarketplaceName(name);
|
|
194
|
+
const known = readKnownMarketplaces(root);
|
|
195
|
+
const entry = known.marketplaces[name];
|
|
196
|
+
if (!entry) {
|
|
197
|
+
throw new Error(`Marketplace "${name}" is not registered. Run: dd marketplace add`);
|
|
198
|
+
}
|
|
199
|
+
const manifest = await fetchMarketplaceManifest(entry.source, entry.manifestPath, entry.ref);
|
|
200
|
+
writeCachedMarketplace(root, name, manifest);
|
|
201
|
+
// Update metadata in known file
|
|
202
|
+
entry.cachedAt = new Date().toISOString();
|
|
203
|
+
entry.extensionCount = manifest.extensions.length;
|
|
204
|
+
writeKnownMarketplaces(root, known);
|
|
205
|
+
return manifest;
|
|
206
|
+
}
|
|
207
|
+
// ---------------------------------------------------------------------------
|
|
208
|
+
// Query helpers
|
|
209
|
+
// ---------------------------------------------------------------------------
|
|
210
|
+
/**
|
|
211
|
+
* Get all extensions across all cached marketplaces.
|
|
212
|
+
*/
|
|
213
|
+
export function getAllCachedExtensions(root) {
|
|
214
|
+
const known = readKnownMarketplaces(root);
|
|
215
|
+
const results = [];
|
|
216
|
+
for (const [name] of Object.entries(known.marketplaces)) {
|
|
217
|
+
const cached = readCachedMarketplace(root, name);
|
|
218
|
+
if (!cached)
|
|
219
|
+
continue;
|
|
220
|
+
for (const ext of cached.manifest.extensions) {
|
|
221
|
+
results.push({ ...ext, marketplace: name });
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return results;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Resolve an extension name from a specific marketplace.
|
|
228
|
+
* Returns null if not found.
|
|
229
|
+
*/
|
|
230
|
+
export function resolveExtensionFromMarketplace(root, extensionName, marketplaceName) {
|
|
231
|
+
const cached = readCachedMarketplace(root, marketplaceName);
|
|
232
|
+
if (!cached)
|
|
233
|
+
return null;
|
|
234
|
+
const ext = cached.manifest.extensions.find((e) => e.name === extensionName);
|
|
235
|
+
if (!ext)
|
|
236
|
+
return null;
|
|
237
|
+
return { ...ext, marketplace: marketplaceName };
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Resolve an extension name across all known marketplaces.
|
|
241
|
+
* Returns first match (priority: verified first, then alphabetical marketplace order).
|
|
242
|
+
*/
|
|
243
|
+
export function resolveExtension(root, extensionName) {
|
|
244
|
+
const all = getAllCachedExtensions(root);
|
|
245
|
+
// Filter by name
|
|
246
|
+
const matches = all.filter((e) => e.name === extensionName);
|
|
247
|
+
if (matches.length === 0)
|
|
248
|
+
return null;
|
|
249
|
+
// Prefer verified
|
|
250
|
+
const verified = matches.find((e) => e.verified);
|
|
251
|
+
if (verified)
|
|
252
|
+
return verified;
|
|
253
|
+
return matches[0];
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Convert a marketplace extension source to a `gh:owner/repo/path#ref` string
|
|
257
|
+
* compatible with the existing `dd add` pipeline.
|
|
258
|
+
*
|
|
259
|
+
* Prefers the pinned SHA over ref for reproducibility.
|
|
260
|
+
*/
|
|
261
|
+
export function extensionSourceToGhString(ext) {
|
|
262
|
+
const { source } = ext;
|
|
263
|
+
let ghString = `gh:${source.url}`;
|
|
264
|
+
if (source.path)
|
|
265
|
+
ghString += `/${source.path}`;
|
|
266
|
+
const refPart = source.sha ?? source.ref;
|
|
267
|
+
if (refPart)
|
|
268
|
+
ghString += `#${refPart}`;
|
|
269
|
+
return ghString;
|
|
270
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { WorkspacePaths } from '../paths.js';
|
|
2
|
+
export declare const DEFAULT_DATABASE_URL = "postgresql://doubledigit:doubledigit@localhost:5432/doubledigit";
|
|
3
|
+
export declare const DEFAULT_APP_URL = "http://localhost:3000";
|
|
4
|
+
export declare const DEFAULT_EMBEDDED_POSTGRES_PORT = 54329;
|
|
5
|
+
export declare const DEFAULT_EMBEDDED_POSTGRES_INSTANCE_ID = "default";
|
|
6
|
+
export declare const DEFAULT_EMBEDDED_POSTGRES_HOME: string;
|
|
7
|
+
export declare const DEFAULT_EMBEDDED_POSTGRES_DATA_DIR: string;
|
|
8
|
+
export interface CommandCaptureResult {
|
|
9
|
+
ok: boolean;
|
|
10
|
+
output: string;
|
|
11
|
+
}
|
|
12
|
+
export interface DatabaseInfo {
|
|
13
|
+
host: string;
|
|
14
|
+
port: number;
|
|
15
|
+
database: string;
|
|
16
|
+
username?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface EnvSetupResult {
|
|
19
|
+
envPath: string;
|
|
20
|
+
created: boolean;
|
|
21
|
+
updatedKeys: string[];
|
|
22
|
+
env: Record<string, string>;
|
|
23
|
+
}
|
|
24
|
+
export interface EnvInspectionResult {
|
|
25
|
+
envPath: string;
|
|
26
|
+
exists: boolean;
|
|
27
|
+
env: Record<string, string>;
|
|
28
|
+
}
|
|
29
|
+
export type DatabaseMode = 'external' | 'embedded' | 'docker';
|
|
30
|
+
export interface DatabaseRuntime {
|
|
31
|
+
mode: DatabaseMode;
|
|
32
|
+
databaseUrl: string;
|
|
33
|
+
source: string;
|
|
34
|
+
reachable: boolean;
|
|
35
|
+
embeddedDataDir?: string;
|
|
36
|
+
embeddedPort?: number;
|
|
37
|
+
dockerPort?: number;
|
|
38
|
+
}
|
|
39
|
+
export interface EnsureLocalEnvOptions {
|
|
40
|
+
databaseMode?: DatabaseMode;
|
|
41
|
+
databaseUrl?: string;
|
|
42
|
+
embeddedDataDir?: string;
|
|
43
|
+
embeddedPort?: number;
|
|
44
|
+
dockerPort?: number;
|
|
45
|
+
}
|
|
46
|
+
interface EnsureDatabaseOptions {
|
|
47
|
+
yes?: boolean;
|
|
48
|
+
allowDockerFallback?: boolean;
|
|
49
|
+
}
|
|
50
|
+
export interface DatabasePreference {
|
|
51
|
+
mode: DatabaseMode;
|
|
52
|
+
reason: string;
|
|
53
|
+
databaseUrl?: string;
|
|
54
|
+
}
|
|
55
|
+
export declare function getEnvPath(paths: WorkspacePaths): string;
|
|
56
|
+
export declare function getEnvExamplePath(paths: WorkspacePaths): string;
|
|
57
|
+
export declare function getGeneratedTypesPath(paths: WorkspacePaths): string;
|
|
58
|
+
export declare function commandExists(command: string): boolean;
|
|
59
|
+
export declare function dockerAvailable(): boolean;
|
|
60
|
+
export declare function runChecked(command: string, args: string[], cwd: string, label: string, envOverrides?: Record<string, string>): void;
|
|
61
|
+
export declare function captureCommand(command: string, args: string[], cwd: string, envOverrides?: Record<string, string>): CommandCaptureResult;
|
|
62
|
+
export declare function readEnvFile(filePath: string): Record<string, string>;
|
|
63
|
+
export declare function isPlaceholderValue(key: string, value: string | undefined): boolean;
|
|
64
|
+
export declare function ensureLocalEnv(paths: WorkspacePaths, options?: EnsureLocalEnvOptions): EnvSetupResult;
|
|
65
|
+
export declare function inspectLocalEnv(paths: WorkspacePaths): EnvInspectionResult;
|
|
66
|
+
export declare function parseDatabaseUrl(databaseUrl: string): DatabaseInfo;
|
|
67
|
+
export declare function checkDatabaseReachability(databaseUrl: string): Promise<boolean>;
|
|
68
|
+
export declare function confirmYesNo(message: string): Promise<boolean>;
|
|
69
|
+
export declare function waitForDockerDatabase(paths: WorkspacePaths, envOverrides?: Record<string, string>): Promise<boolean>;
|
|
70
|
+
export declare function startDevServer(paths: WorkspacePaths, envOverrides?: Record<string, string>): Promise<void>;
|
|
71
|
+
export declare function printNextSteps(): void;
|
|
72
|
+
export declare function resolveDatabasePreference(env: Record<string, string>): DatabasePreference;
|
|
73
|
+
export declare function probeEmbeddedPostgresSupport(): Promise<{
|
|
74
|
+
supported: boolean;
|
|
75
|
+
reason?: string;
|
|
76
|
+
}>;
|
|
77
|
+
export declare function ensureDatabaseReady(paths: WorkspacePaths, options?: EnsureDatabaseOptions): Promise<DatabaseRuntime>;
|
|
78
|
+
export declare function localDependenciesInstalled(paths: WorkspacePaths): boolean;
|
|
79
|
+
export declare function databaseRuntimeToEnvOptions(runtime: DatabaseRuntime): EnsureLocalEnvOptions;
|
|
80
|
+
export declare function applyRuntimeDatabaseUrl(env: Record<string, string>, runtime: DatabaseRuntime): Record<string, string>;
|
|
81
|
+
export declare function trimOutput(output: string, maxLines?: number): string;
|
|
82
|
+
export declare function requiredEnvKeysMissing(env: Record<string, string>): string[];
|
|
83
|
+
export {};
|
|
84
|
+
//# sourceMappingURL=onboarding.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"onboarding.d.ts","sourceRoot":"","sources":["../../src/lib/onboarding.ts"],"names":[],"mappings":"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"}
|