@stream44.studio/t44-docker.com 0.1.0-rc.3
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/.dco-signatures +9 -0
- package/.github/workflows/dco.yaml +12 -0
- package/.github/workflows/gordian-open-integrity.yaml +13 -0
- package/.github/workflows/test.yaml +29 -0
- package/.o/GordianOpenIntegrity-CurrentLifehash.svg +1026 -0
- package/.o/GordianOpenIntegrity-InceptionLifehash.svg +1026 -0
- package/.o/GordianOpenIntegrity.yaml +21 -0
- package/.o/stream44.studio/assets/Icon-v1.svg +1170 -0
- package/.repo-identifier +1 -0
- package/DCO.md +34 -0
- package/LICENSE.txt +23 -0
- package/README.md +73 -0
- package/caps/Cli.test.ts +116 -0
- package/caps/Cli.ts +134 -0
- package/caps/Container.test.ts +168 -0
- package/caps/Container.ts +484 -0
- package/caps/ContainerContext.test.ts +78 -0
- package/caps/ContainerContext.ts +111 -0
- package/caps/Containers.test.ts +59 -0
- package/caps/Containers.ts +47 -0
- package/caps/Hub.test.ts +107 -0
- package/caps/Hub.ts +367 -0
- package/caps/Image/tpl/Dockerfile.alpine +40 -0
- package/caps/Image/tpl/Dockerfile.distroless +45 -0
- package/caps/Image/tpl/package.json +8 -0
- package/caps/Image.test.ts +269 -0
- package/caps/Image.ts +623 -0
- package/caps/ImageContext.test.ts +130 -0
- package/caps/ImageContext.ts +126 -0
- package/caps/Project.test.ts +267 -0
- package/caps/Project.ts +304 -0
- package/lib/waitForFetch.ts +95 -0
- package/package.json +19 -0
- package/structs/Hub/WorkspaceConnectionConfig.ts +53 -0
- package/tsconfig.json +28 -0
package/caps/Project.ts
ADDED
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
export async function capsule({
|
|
2
|
+
encapsulate,
|
|
3
|
+
CapsulePropertyTypes,
|
|
4
|
+
makeImportStack
|
|
5
|
+
}: {
|
|
6
|
+
encapsulate: any
|
|
7
|
+
CapsulePropertyTypes: any
|
|
8
|
+
makeImportStack: any
|
|
9
|
+
}) {
|
|
10
|
+
|
|
11
|
+
return encapsulate({
|
|
12
|
+
'#@stream44.studio/encapsulate/spine-contracts/CapsuleSpineContract.v0': {
|
|
13
|
+
'#@stream44.studio/encapsulate/structs/Capsule': {},
|
|
14
|
+
'#': {
|
|
15
|
+
test: {
|
|
16
|
+
type: CapsulePropertyTypes.Mapping,
|
|
17
|
+
value: 't44/caps/ProjectTest',
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
cli: {
|
|
21
|
+
type: CapsulePropertyTypes.Mapping,
|
|
22
|
+
value: './Cli',
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
image: {
|
|
26
|
+
type: CapsulePropertyTypes.Mapping,
|
|
27
|
+
value: './Image',
|
|
28
|
+
options: { /* requires new instance */ },
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
container: {
|
|
32
|
+
type: CapsulePropertyTypes.Mapping,
|
|
33
|
+
value: './Container',
|
|
34
|
+
options: { /* requires new instance */ },
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
// --- Project-level config ---
|
|
38
|
+
|
|
39
|
+
dispose: {
|
|
40
|
+
type: CapsulePropertyTypes.Literal,
|
|
41
|
+
value: false as boolean,
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
// Internal state
|
|
45
|
+
_devContainerId: {
|
|
46
|
+
type: CapsulePropertyTypes.Literal,
|
|
47
|
+
value: undefined as string | undefined,
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Get development container configuration as a derived ContainerContext plain object.
|
|
52
|
+
* Merges dev defaults (image tag, sanitized name, detach, waitFor) on top of
|
|
53
|
+
* whatever is already configured on container.context.
|
|
54
|
+
*/
|
|
55
|
+
getDevelopmentContainerConfig: {
|
|
56
|
+
type: CapsulePropertyTypes.Function,
|
|
57
|
+
value: function (this: any): Record<string, any> {
|
|
58
|
+
const imageCtx = this.image.context;
|
|
59
|
+
const imageTag = imageCtx.getImageTag({
|
|
60
|
+
variant: imageCtx.variant || 'alpine',
|
|
61
|
+
arch: imageCtx.arch || this.cli.getCurrentPlatformArch(),
|
|
62
|
+
});
|
|
63
|
+
const sanitizedName = imageTag.replace(/[^a-zA-Z0-9_.-]/g, '-') + '-dev';
|
|
64
|
+
|
|
65
|
+
return this.container.context.derive({
|
|
66
|
+
image: imageTag,
|
|
67
|
+
name: sanitizedName,
|
|
68
|
+
detach: true,
|
|
69
|
+
waitFor: this.container.context.waitFor ?? 'READY',
|
|
70
|
+
waitTimeout: this.container.context.waitTimeout ?? 30000,
|
|
71
|
+
verbose: this.container.context.verbose ?? imageCtx.verbose,
|
|
72
|
+
showOutput: this.container.context.showOutput ?? imageCtx.verbose,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Build development image (current platform, alpine variant)
|
|
79
|
+
*/
|
|
80
|
+
buildDev: {
|
|
81
|
+
type: CapsulePropertyTypes.Function,
|
|
82
|
+
value: async function (this: any, options?: {
|
|
83
|
+
files?: Record<string, any>;
|
|
84
|
+
tagLatest?: boolean;
|
|
85
|
+
attestations?: { sbom?: boolean; provenance?: boolean };
|
|
86
|
+
}): Promise<{ imageTag: string }> {
|
|
87
|
+
const ctx = this.image.context;
|
|
88
|
+
const files = (ctx.files || options?.files)
|
|
89
|
+
? { ...ctx.files, ...options?.files }
|
|
90
|
+
: undefined;
|
|
91
|
+
|
|
92
|
+
return await this.image.buildVariant({
|
|
93
|
+
variant: ctx.variant || 'alpine',
|
|
94
|
+
arch: this.cli.getCurrentPlatformArch(),
|
|
95
|
+
files,
|
|
96
|
+
tagLatest: options?.tagLatest,
|
|
97
|
+
attestations: options?.attestations ?? ctx.attestations,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Build all distribution images (all variants × all archs)
|
|
104
|
+
*/
|
|
105
|
+
buildDistribution: {
|
|
106
|
+
type: CapsulePropertyTypes.Function,
|
|
107
|
+
value: async function (this: any, options?: {
|
|
108
|
+
files?: Record<string, any>;
|
|
109
|
+
tagLatest?: boolean;
|
|
110
|
+
attestations?: { sbom?: boolean; provenance?: boolean };
|
|
111
|
+
}): Promise<{ imageTag: string }[]> {
|
|
112
|
+
const ctx = this.image.context;
|
|
113
|
+
const files = (ctx.files || options?.files)
|
|
114
|
+
? { ...ctx.files, ...options?.files }
|
|
115
|
+
: undefined;
|
|
116
|
+
|
|
117
|
+
const results: { imageTag: string }[] = [];
|
|
118
|
+
|
|
119
|
+
const archKeys = ctx.arch
|
|
120
|
+
? [ctx.arch]
|
|
121
|
+
: Object.keys(this.cli.DOCKER_ARCHS);
|
|
122
|
+
const variantKeys = ctx.variant
|
|
123
|
+
? [ctx.variant]
|
|
124
|
+
: Object.keys(ctx.DOCKERFILE_VARIANTS);
|
|
125
|
+
|
|
126
|
+
for (const variantKey of variantKeys) {
|
|
127
|
+
for (const archKey of archKeys) {
|
|
128
|
+
results.push(await this.image.buildVariant({
|
|
129
|
+
variant: variantKey,
|
|
130
|
+
arch: archKey,
|
|
131
|
+
files,
|
|
132
|
+
tagLatest: options?.tagLatest,
|
|
133
|
+
attestations: options?.attestations ?? ctx.attestations,
|
|
134
|
+
}));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return results;
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Run development container (with SIGINT/SIGTERM handlers)
|
|
144
|
+
*/
|
|
145
|
+
runDev: {
|
|
146
|
+
type: CapsulePropertyTypes.Function,
|
|
147
|
+
value: async function (this: any, options?: { showOutput?: boolean }): Promise<{
|
|
148
|
+
containerId: string;
|
|
149
|
+
stop: () => Promise<void>;
|
|
150
|
+
ensureRunning: () => Promise<boolean>;
|
|
151
|
+
}> {
|
|
152
|
+
const containerContext = {
|
|
153
|
+
...this.getDevelopmentContainerConfig(),
|
|
154
|
+
...(options?.showOutput !== undefined ? { showOutput: options.showOutput } : {}),
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
let signalReceived = false;
|
|
158
|
+
let signalName = '';
|
|
159
|
+
|
|
160
|
+
const stop = async () => {
|
|
161
|
+
await this.container.cleanup(containerContext);
|
|
162
|
+
this._devContainerId = undefined;
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const signalHandler = (signal: string) => {
|
|
166
|
+
console.error(`\n[runDev] Received ${signal}, stopping container...`);
|
|
167
|
+
signalReceived = true;
|
|
168
|
+
signalName = signal;
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
process.on('SIGINT', signalHandler);
|
|
172
|
+
process.on('SIGTERM', signalHandler);
|
|
173
|
+
|
|
174
|
+
await this.container.ensureStopped(containerContext);
|
|
175
|
+
if (containerContext.verbose) console.log(`\nRunning container from image: ${containerContext.image}...`);
|
|
176
|
+
const containerId = await this.container.run(containerContext);
|
|
177
|
+
if (containerContext.verbose) console.log(`✅ Container started: ${containerId}`);
|
|
178
|
+
this._devContainerId = containerId;
|
|
179
|
+
|
|
180
|
+
if (signalReceived) {
|
|
181
|
+
console.error(`[runDev] Signal ${signalName} was received during startup, stopping container...`);
|
|
182
|
+
await stop();
|
|
183
|
+
process.exit(0);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const ensureRunning = async () => {
|
|
187
|
+
const isRunning = await this.container.isRunning({
|
|
188
|
+
...containerContext,
|
|
189
|
+
retryDelayMs: 2000,
|
|
190
|
+
requestTimeoutMs: 5000,
|
|
191
|
+
timeoutMs: 60000,
|
|
192
|
+
});
|
|
193
|
+
if (!isRunning) {
|
|
194
|
+
throw new Error(`Container ${containerId} failed to respond`);
|
|
195
|
+
}
|
|
196
|
+
return true;
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const runningSignalHandler = async (signal: string) => {
|
|
200
|
+
console.error(`\n[runDev] Received ${signal}, stopping container...`);
|
|
201
|
+
await stop();
|
|
202
|
+
process.exit(0);
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
process.off('SIGINT', signalHandler);
|
|
206
|
+
process.off('SIGTERM', signalHandler);
|
|
207
|
+
process.on('SIGINT', runningSignalHandler);
|
|
208
|
+
process.on('SIGTERM', runningSignalHandler);
|
|
209
|
+
|
|
210
|
+
return { containerId, stop, ensureRunning };
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Ensure dev container is running
|
|
216
|
+
*/
|
|
217
|
+
ensureDevRunning: {
|
|
218
|
+
type: CapsulePropertyTypes.Function,
|
|
219
|
+
value: async function (this: any): Promise<boolean> {
|
|
220
|
+
if (!this._devContainerId) {
|
|
221
|
+
throw new Error('Container must be started first using runDev()');
|
|
222
|
+
}
|
|
223
|
+
const containerContext = this.getDevelopmentContainerConfig();
|
|
224
|
+
const isRunning = await this.container.isRunning({
|
|
225
|
+
...containerContext,
|
|
226
|
+
retryDelayMs: 2000,
|
|
227
|
+
requestTimeoutMs: 5000,
|
|
228
|
+
timeoutMs: 60000,
|
|
229
|
+
});
|
|
230
|
+
if (!isRunning) {
|
|
231
|
+
throw new Error('Container failed to respond after 60 seconds');
|
|
232
|
+
}
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Stop dev container
|
|
239
|
+
*/
|
|
240
|
+
stopDev: {
|
|
241
|
+
type: CapsulePropertyTypes.Function,
|
|
242
|
+
value: async function (this: any): Promise<void> {
|
|
243
|
+
if (!this._devContainerId) {
|
|
244
|
+
throw new Error('Container must be started first using runDev()');
|
|
245
|
+
}
|
|
246
|
+
await this.container.cleanup();
|
|
247
|
+
this._devContainerId = undefined;
|
|
248
|
+
}
|
|
249
|
+
},
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Retag images from a source org/repo to this project's org/repo
|
|
253
|
+
*/
|
|
254
|
+
retagImages: {
|
|
255
|
+
type: CapsulePropertyTypes.Function,
|
|
256
|
+
value: async function (this: any, { organization, repository }: { organization: string; repository: string }): Promise<void> {
|
|
257
|
+
const ctx = this.image.context;
|
|
258
|
+
if (ctx.verbose) {
|
|
259
|
+
console.log(`\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
|
|
260
|
+
console.log(`Retagging images from ${organization}/${repository} to ${ctx.organization}/${ctx.repository}`);
|
|
261
|
+
console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const tags = await this.image.getTags({ organization, repository });
|
|
265
|
+
|
|
266
|
+
if (ctx.verbose) console.log(`Found ${tags.length} tags to retag`);
|
|
267
|
+
|
|
268
|
+
for (const tagInfo of tags) {
|
|
269
|
+
const tagSuffix = tagInfo.tag.split(':')[1];
|
|
270
|
+
if (!tagSuffix) continue;
|
|
271
|
+
|
|
272
|
+
const sourceImageTag = `${organization}/${repository}:${tagSuffix}`;
|
|
273
|
+
const targetImageTag = `${ctx.organization}/${ctx.repository}:${tagSuffix}`;
|
|
274
|
+
|
|
275
|
+
if (ctx.verbose) console.log(` Tagging: ${sourceImageTag} -> ${targetImageTag}`);
|
|
276
|
+
|
|
277
|
+
await this.cli.tagImage({ sourceImage: sourceImageTag, targetImage: targetImageTag });
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (ctx.verbose) console.log(`✅ Retagged ${tags.length} images`);
|
|
281
|
+
}
|
|
282
|
+
},
|
|
283
|
+
|
|
284
|
+
Dispose: {
|
|
285
|
+
type: CapsulePropertyTypes.Dispose,
|
|
286
|
+
value: async function (this: any): Promise<void> {
|
|
287
|
+
if (!this.dispose || !this._devContainerId) return;
|
|
288
|
+
try {
|
|
289
|
+
const containerContext = this.getDevelopmentContainerConfig();
|
|
290
|
+
await this.container.cleanup(containerContext);
|
|
291
|
+
} catch (error) {
|
|
292
|
+
if (this.image.context.verbose) console.log(`Warning: Failed to cleanup dev container on dispose: ${error}`);
|
|
293
|
+
}
|
|
294
|
+
this._devContainerId = undefined;
|
|
295
|
+
}
|
|
296
|
+
},
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}, {
|
|
300
|
+
importMeta: import.meta,
|
|
301
|
+
importStack: makeImportStack(),
|
|
302
|
+
capsuleName: '@stream44.studio/t44-docker.com/caps/Project',
|
|
303
|
+
})
|
|
304
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wait for a URL to respond with a specific condition
|
|
3
|
+
*/
|
|
4
|
+
export interface WaitForFetchOptions {
|
|
5
|
+
url: string;
|
|
6
|
+
method?: string; // HTTP method (GET, POST, etc.)
|
|
7
|
+
headers?: Record<string, string>; // Request headers
|
|
8
|
+
body?: string; // Request body
|
|
9
|
+
status: true | false | number; // true = any response, false = no response (fetch fails), number = specific status code
|
|
10
|
+
retryDelayMs?: number;
|
|
11
|
+
requestTimeoutMs?: number;
|
|
12
|
+
timeoutMs?: number;
|
|
13
|
+
verbose?: boolean;
|
|
14
|
+
returnResponse?: boolean; // If true, return the Response object instead of boolean
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function waitForFetch(options: WaitForFetchOptions & { returnResponse: true }): Promise<Response>;
|
|
18
|
+
export async function waitForFetch(options: WaitForFetchOptions & { returnResponse?: false }): Promise<boolean>;
|
|
19
|
+
export async function waitForFetch(options: WaitForFetchOptions): Promise<boolean | Response> {
|
|
20
|
+
const {
|
|
21
|
+
url,
|
|
22
|
+
method = 'GET',
|
|
23
|
+
headers,
|
|
24
|
+
body,
|
|
25
|
+
status,
|
|
26
|
+
retryDelayMs = 1000,
|
|
27
|
+
requestTimeoutMs = 2000,
|
|
28
|
+
timeoutMs = 30000,
|
|
29
|
+
verbose = false,
|
|
30
|
+
returnResponse = false
|
|
31
|
+
} = options;
|
|
32
|
+
|
|
33
|
+
const startTime = Date.now();
|
|
34
|
+
let attemptCount = 0;
|
|
35
|
+
|
|
36
|
+
while (Date.now() - startTime < timeoutMs) {
|
|
37
|
+
attemptCount++;
|
|
38
|
+
const elapsed = Date.now() - startTime;
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const response = await fetch(url, {
|
|
42
|
+
method,
|
|
43
|
+
headers,
|
|
44
|
+
body,
|
|
45
|
+
signal: AbortSignal.timeout(requestTimeoutMs)
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Check if condition is met
|
|
49
|
+
if (status === true) {
|
|
50
|
+
// Any response is success
|
|
51
|
+
if (verbose) {
|
|
52
|
+
console.log(`[waitForFetch] URL ${url} responded (status: ${response.status}) after ${attemptCount} attempts (${elapsed}ms)`);
|
|
53
|
+
}
|
|
54
|
+
return returnResponse ? response : true;
|
|
55
|
+
} else if (typeof status === 'number') {
|
|
56
|
+
// Specific status code required
|
|
57
|
+
if (response.status === status) {
|
|
58
|
+
if (verbose) {
|
|
59
|
+
console.log(`[waitForFetch] URL ${url} responded with status ${status} after ${attemptCount} attempts (${elapsed}ms)`);
|
|
60
|
+
}
|
|
61
|
+
return returnResponse ? response : true;
|
|
62
|
+
} else {
|
|
63
|
+
if (verbose) {
|
|
64
|
+
console.log(`[waitForFetch] Attempt ${attemptCount}: Got status ${response.status}, expected ${status} (${elapsed}ms)`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// If status === false, we want fetch to fail, so getting a response means condition not met
|
|
69
|
+
} catch (error) {
|
|
70
|
+
// Fetch failed
|
|
71
|
+
if (status === false) {
|
|
72
|
+
// We want fetch to fail, so this is success
|
|
73
|
+
if (verbose) {
|
|
74
|
+
console.log(`[waitForFetch] URL ${url} is not responding (as expected) after ${attemptCount} attempts (${elapsed}ms)`);
|
|
75
|
+
}
|
|
76
|
+
return true;
|
|
77
|
+
} else {
|
|
78
|
+
if (verbose) {
|
|
79
|
+
console.log(`[waitForFetch] Attempt ${attemptCount}: Request failed (${elapsed}ms)`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Wait before next attempt, but don't exceed total timeout
|
|
85
|
+
const remainingTime = timeoutMs - (Date.now() - startTime);
|
|
86
|
+
if (remainingTime > 0) {
|
|
87
|
+
await new Promise(resolve => setTimeout(resolve, Math.min(retryDelayMs, remainingTime)));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (verbose) {
|
|
92
|
+
console.log(`[waitForFetch] Timeout reached after ${attemptCount} attempts (${Date.now() - startTime}ms)`);
|
|
93
|
+
}
|
|
94
|
+
return false;
|
|
95
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@stream44.studio/t44-docker.com",
|
|
3
|
+
"version": "0.1.0-rc.3",
|
|
4
|
+
"private": false,
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"test": "bun test"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"t44": "^0.4.0-rc.17",
|
|
12
|
+
"@stream44.studio/encapsulate": "^0.4.0-rc.19"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@types/bun": "^1.3.4",
|
|
16
|
+
"@types/node": "^25.0.3",
|
|
17
|
+
"bun-types": "^1.3.4"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
|
|
2
|
+
export async function capsule({
|
|
3
|
+
encapsulate,
|
|
4
|
+
CapsulePropertyTypes,
|
|
5
|
+
makeImportStack
|
|
6
|
+
}: any) {
|
|
7
|
+
return encapsulate({
|
|
8
|
+
'#@stream44.studio/encapsulate/spine-contracts/CapsuleSpineContract.v0': {
|
|
9
|
+
'#t44/caps/ConfigSchemaStruct': {
|
|
10
|
+
as: 'schema',
|
|
11
|
+
options: {
|
|
12
|
+
'#': {
|
|
13
|
+
schema: {
|
|
14
|
+
type: 'object',
|
|
15
|
+
properties: {
|
|
16
|
+
username: {
|
|
17
|
+
type: 'string',
|
|
18
|
+
title: 'Docker Hub Username',
|
|
19
|
+
description: 'Your Docker Hub username from https://hub.docker.com',
|
|
20
|
+
minLength: 1,
|
|
21
|
+
},
|
|
22
|
+
password: {
|
|
23
|
+
type: 'string',
|
|
24
|
+
title: 'Docker Hub Password or Personal Access Token',
|
|
25
|
+
description: 'Your Docker Hub password or a Personal Access Token (PAT) from https://hub.docker.com/settings/security',
|
|
26
|
+
minLength: 1,
|
|
27
|
+
},
|
|
28
|
+
organization: {
|
|
29
|
+
type: 'string',
|
|
30
|
+
title: 'Docker Hub Organization (optional)',
|
|
31
|
+
description: 'Your Docker Hub organization name. Leave empty to use your username as the namespace.',
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
required: ['username', 'password']
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
'#': {
|
|
40
|
+
capsuleName: {
|
|
41
|
+
type: CapsulePropertyTypes.Literal,
|
|
42
|
+
value: capsule['#']
|
|
43
|
+
},
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}, {
|
|
47
|
+
extendsCapsule: 't44/caps/WorkspaceConnection',
|
|
48
|
+
importMeta: import.meta,
|
|
49
|
+
importStack: makeImportStack(),
|
|
50
|
+
capsuleName: capsule['#'],
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
capsule['#'] = '@stream44.studio/t44-docker.com/structs/Hub/WorkspaceConnectionConfig'
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../../tsconfig.paths.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"target": "es2021",
|
|
5
|
+
"module": "esnext",
|
|
6
|
+
"lib": [
|
|
7
|
+
"ES2021",
|
|
8
|
+
"DOM"
|
|
9
|
+
],
|
|
10
|
+
"types": [
|
|
11
|
+
"bun",
|
|
12
|
+
"node"
|
|
13
|
+
],
|
|
14
|
+
"moduleResolution": "bundler",
|
|
15
|
+
"strict": true,
|
|
16
|
+
"esModuleInterop": true,
|
|
17
|
+
"skipLibCheck": true,
|
|
18
|
+
"forceConsistentCasingInFileNames": true,
|
|
19
|
+
"resolveJsonModule": true,
|
|
20
|
+
"allowSyntheticDefaultImports": true
|
|
21
|
+
},
|
|
22
|
+
"include": [
|
|
23
|
+
"**/*.ts"
|
|
24
|
+
],
|
|
25
|
+
"exclude": [
|
|
26
|
+
"node_modules"
|
|
27
|
+
]
|
|
28
|
+
}
|