hologit 0.43.2 → 0.45.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/index.d.ts CHANGED
@@ -1,6 +1,15 @@
1
+ declare module 'git-client' {
2
+ export class Git {
3
+ constructor(options: { gitDir: string; workTree?: string });
4
+ }
5
+ }
6
+
7
+ declare module '@iarna/toml' {
8
+ export function parse(content: string): any;
9
+ }
10
+
1
11
  declare module 'hologit' {
2
12
  import { Git as GitClient } from 'git-client';
3
- import { Docker } from 'dockerode';
4
13
 
5
14
  export interface GitOptions {
6
15
  gitDir: string;
@@ -59,16 +68,40 @@ declare module 'hologit' {
59
68
  cacheTo?: string | null;
60
69
  }
61
70
 
71
+ export interface DockerExecOptions {
72
+ $relayStderr?: boolean;
73
+ $relayStdout?: boolean;
74
+ }
75
+
76
+ export interface StudioContainer {
77
+ id?: string;
78
+ type?: 'studio';
79
+ env?: { [key: string]: string };
80
+ defaultUser?: string;
81
+ }
82
+
83
+ export interface HoloSpec {
84
+ holospec: {
85
+ lens: {
86
+ input: string;
87
+ container?: string;
88
+ package?: string;
89
+ command?: string;
90
+ [key: string]: any;
91
+ };
92
+ };
93
+ }
94
+
62
95
  export class Git {
63
- static async get(): Promise<typeof GitClient>;
96
+ static get(): Promise<typeof GitClient>;
64
97
  constructor(options: GitOptions);
65
98
  gitDir: string;
66
99
  workTree?: string;
67
100
  }
68
101
 
69
102
  export class BlobObject {
70
- static async write(repo: Repo, content: string): Promise<BlobObject>;
71
- static async writeFromFile(repo: Repo, filePath: string): Promise<BlobObject>;
103
+ static write(repo: Repo, content: string): Promise<BlobObject>;
104
+ static writeFromFile(repo: Repo, filePath: string): Promise<BlobObject>;
72
105
 
73
106
  constructor(repo: Repo, options: GitObjectOptions);
74
107
 
@@ -78,12 +111,12 @@ declare module 'hologit' {
78
111
  isBlob: boolean;
79
112
  type: 'blob';
80
113
 
81
- async read(): Promise<string>;
114
+ read(): Promise<string>;
82
115
  }
83
116
 
84
117
  export class TreeObject {
85
118
  static getEmptyTreeHash(): string;
86
- static async createFromRef(repo: Repo, ref: string): Promise<TreeObject>;
119
+ static createFromRef(repo: Repo, ref: string): Promise<TreeObject>;
87
120
 
88
121
  constructor(repo: Repo, options?: { hash?: string; parent?: TreeObject | null });
89
122
 
@@ -95,19 +128,19 @@ declare module 'hologit' {
95
128
  type: 'tree';
96
129
  mode: '040000';
97
130
 
98
- async getHash(): Promise<string>;
131
+ getHash(): Promise<string>;
99
132
  getWrittenHash(): string | null;
100
133
  markDirty(): void;
101
- async getChild(childPath: string): Promise<TreeObject | BlobObject | CommitObject | null>;
102
- async writeChild(childPath: string, content: string | BlobObject): Promise<BlobObject>;
103
- async getChildren(): Promise<{ [key: string]: TreeObject | BlobObject | CommitObject }>;
104
- async getBlobMap(): Promise<{ [key: string]: BlobObject }>;
105
- async deleteChild(childPath: string): Promise<void>;
106
- async getSubtree(subtreePath: string, create?: boolean): Promise<TreeObject | null>;
107
- async getSubtreeStack(subtreePath: string, create?: boolean): Promise<TreeObject[] | null>;
108
- async write(): Promise<string>;
109
- async merge(input: TreeObject, options?: MergeOptions, basePath?: string, preloadChildren?: boolean): Promise<void>;
110
- async clone(): Promise<TreeObject>;
134
+ getChild(childPath: string): Promise<TreeObject | BlobObject | CommitObject | null>;
135
+ writeChild(childPath: string, content: string | BlobObject): Promise<BlobObject>;
136
+ getChildren(): Promise<{ [key: string]: TreeObject | BlobObject | CommitObject }>;
137
+ getBlobMap(): Promise<{ [key: string]: BlobObject }>;
138
+ deleteChild(childPath: string): Promise<void>;
139
+ getSubtree(subtreePath: string, create?: boolean): Promise<TreeObject | null>;
140
+ getSubtreeStack(subtreePath: string, create?: boolean): Promise<TreeObject[] | null>;
141
+ write(): Promise<string>;
142
+ merge(input: TreeObject, options?: MergeOptions, basePath?: string, preloadChildren?: boolean): Promise<void>;
143
+ clone(): Promise<TreeObject>;
111
144
  }
112
145
 
113
146
  export class CommitObject {
@@ -128,10 +161,10 @@ declare module 'hologit' {
128
161
 
129
162
  getWorkspace(): Workspace;
130
163
  getRepo(): Repo;
131
- async readConfig(): Promise<any>;
132
- async writeConfig(config?: any): Promise<void>;
133
- async getConfig(): Promise<any>;
134
- async getCachedConfig(): Promise<any>;
164
+ readConfig(): Promise<any>;
165
+ writeConfig(config?: any): Promise<void>;
166
+ getConfig(): Promise<any>;
167
+ getCachedConfig(): Promise<any>;
135
168
  }
136
169
 
137
170
  export class Branch extends Configurable {
@@ -141,17 +174,17 @@ declare module 'hologit' {
141
174
 
142
175
  getKind(): 'holobranch';
143
176
  getConfigPath(): string;
144
- async isDefined(): Promise<boolean>;
177
+ isDefined(): Promise<boolean>;
145
178
  getMapping(key: string): Mapping;
146
- async getMappings(): Promise<Map<string, Mapping>>;
147
- async composite(options: {
179
+ getMappings(): Promise<Map<string, Mapping>>;
180
+ composite(options: {
148
181
  outputTree?: TreeObject;
149
182
  fetch?: boolean | string[];
150
183
  cacheFrom?: string | null;
151
184
  cacheTo?: string | null;
152
185
  }): Promise<TreeObject>;
153
186
  getLens(name: string): Lens;
154
- async getLenses(): Promise<Map<string, Lens>>;
187
+ getLenses(): Promise<Map<string, Lens>>;
155
188
  }
156
189
 
157
190
  export class Source extends Configurable {
@@ -163,21 +196,21 @@ declare module 'hologit' {
163
196
 
164
197
  getKind(): 'holosource';
165
198
  getConfigPath(): string;
166
- async getSpec(): Promise<{ hash: string; ref: string; data: any }>;
167
- async getCachedSpec(): Promise<{ hash: string; ref: string; data: any }>;
168
- async queryRef(): Promise<{ hash: string; ref: string } | null>;
169
- async hashWorkTree(): Promise<string | null>;
170
- async getOutputTree(options?: {
199
+ getSpec(): Promise<{ hash: string; ref: string; data: any }>;
200
+ getCachedSpec(): Promise<{ hash: string; ref: string; data: any }>;
201
+ queryRef(): Promise<{ hash: string; ref: string } | null>;
202
+ hashWorkTree(): Promise<string | null>;
203
+ getOutputTree(options?: {
171
204
  working?: boolean | null;
172
205
  fetch?: boolean | string[];
173
206
  cacheFrom?: string | null;
174
207
  cacheTo?: string | null;
175
208
  }): Promise<string>;
176
- async getHead(options?: { required?: boolean; working?: boolean | null }): Promise<string | null>;
177
- async getCachedHead(): Promise<string | null>;
178
- async getBranch(): Promise<string | null>;
179
- async fetch(options?: { depth?: number; unshallow?: boolean | null }, ...refs: string[]): Promise<{ refs: string[] }>;
180
- async checkout(options?: { submodule?: boolean }): Promise<{
209
+ getHead(options?: { required?: boolean; working?: boolean | null }): Promise<string | null>;
210
+ getCachedHead(): Promise<string | null>;
211
+ getBranch(): Promise<string | null>;
212
+ fetch(options?: { depth?: number; unshallow?: boolean | null }, ...refs: string[]): Promise<{ refs: string[] }>;
213
+ checkout(options?: { submodule?: boolean }): Promise<{
181
214
  path: string;
182
215
  head: string;
183
216
  branch: string | null;
@@ -195,13 +228,13 @@ declare module 'hologit' {
195
228
 
196
229
  getKind(): 'hololens';
197
230
  getConfigPath(): string;
198
- async buildInputTree(inputRoot?: TreeObject): Promise<TreeObject>;
199
- async buildSpec(inputTree: TreeObject): Promise<{
231
+ buildInputTree(inputRoot?: TreeObject): Promise<TreeObject>;
232
+ buildSpec(inputTree: TreeObject): Promise<{
200
233
  hash: string;
201
234
  ref: string;
202
235
  data: any;
203
236
  }>;
204
- async executeSpec(specHash: string, options: {
237
+ executeSpec(specHash: string, options: {
205
238
  refresh?: boolean;
206
239
  save?: boolean;
207
240
  repo?: Repo | null;
@@ -209,7 +242,7 @@ declare module 'hologit' {
209
242
  cacheTo?: string | null;
210
243
  }): Promise<string>;
211
244
 
212
- static async executeSpec(specHash: string, options: {
245
+ static executeSpec(specHash: string, options: {
213
246
  refresh?: boolean;
214
247
  save?: boolean;
215
248
  repo?: Repo | null;
@@ -226,18 +259,18 @@ declare module 'hologit' {
226
259
  getWorkspace(): Workspace;
227
260
  getKind(): 'holospace';
228
261
  getConfigPath(): string;
229
- async writeWorkingChanges(): Promise<void>;
262
+ writeWorkingChanges(): Promise<void>;
230
263
  getBranch(name: string): Branch;
231
- async getBranches(): Promise<Map<string, Branch>>;
264
+ getBranches(): Promise<Map<string, Branch>>;
232
265
  getSource(name: string): Source;
233
- async getSources(): Promise<Map<string, Source>>;
234
- async getLayers(): Promise<Map<string, Map<string, Mapping>>>;
266
+ getSources(): Promise<Map<string, Source>>;
267
+ getLayers(): Promise<Map<string, Map<string, Mapping>>>;
235
268
  getLens(name: string): Lens;
236
- async getLenses(): Promise<Map<string, Lens>>;
269
+ getLenses(): Promise<Map<string, Lens>>;
237
270
  }
238
271
 
239
272
  export class Repo {
240
- static async getFromEnvironment(options?: { ref?: string; working?: boolean }): Promise<Repo>;
273
+ static getFromEnvironment(options?: { ref?: string; working?: boolean }): Promise<Repo>;
241
274
 
242
275
  constructor(options: RepoOptions);
243
276
 
@@ -245,27 +278,27 @@ declare module 'hologit' {
245
278
  ref: string;
246
279
  workTree: string | null;
247
280
 
248
- async getWorkspace(): Promise<Workspace>;
249
- async createWorkspaceFromRef(ref: string): Promise<Workspace>;
250
- async createWorkspaceFromTreeHash(hash: string): Promise<Workspace>;
251
- async getGit(): Promise<GitClient>;
252
- async resolveRef(ref?: string | null): Promise<string | null>;
281
+ getWorkspace(): Promise<Workspace>;
282
+ createWorkspaceFromRef(ref: string): Promise<Workspace>;
283
+ createWorkspaceFromTreeHash(hash: string): Promise<Workspace>;
284
+ getGit(): Promise<GitClient>;
285
+ resolveRef(ref?: string | null): Promise<string | null>;
253
286
  createBlob(options: GitObjectOptions): BlobObject;
254
- async writeBlob(content: string): Promise<BlobObject>;
255
- async writeBlobFromFile(filePath: string): Promise<BlobObject>;
287
+ writeBlob(content: string): Promise<BlobObject>;
288
+ writeBlobFromFile(filePath: string): Promise<BlobObject>;
256
289
  createTree(options?: { hash?: string; parent?: TreeObject | null }): TreeObject;
257
- async createTreeFromRef(ref: string): Promise<TreeObject>;
290
+ createTreeFromRef(ref: string): Promise<TreeObject>;
258
291
  createCommit(options: GitObjectOptions): CommitObject;
259
- async hasCommit(commit: string): Promise<boolean>;
260
- async hashWorkTree(): Promise<string>;
261
- async watch(options: { callback: (treeHash: string, commitHash?: string) => void }): Promise<{
292
+ hasCommit(commit: string): Promise<boolean>;
293
+ hashWorkTree(): Promise<string>;
294
+ watch(options: { callback: (treeHash: string, commitHash?: string) => void }): Promise<{
262
295
  watching: Promise<void>;
263
296
  cancel: () => void;
264
297
  }>;
265
298
  }
266
299
 
267
300
  export class Projection {
268
- static async projectBranch(branch: Branch, options?: ProjectionOptions): Promise<string>;
301
+ static projectBranch(branch: Branch, options?: ProjectionOptions): Promise<string>;
269
302
 
270
303
  constructor(options: { branch: Branch });
271
304
 
@@ -273,39 +306,39 @@ declare module 'hologit' {
273
306
  workspace: Workspace;
274
307
  output: Workspace;
275
308
 
276
- async composite(options: {
309
+ composite(options: {
277
310
  fetch?: boolean | string[];
278
311
  cacheFrom?: string | null;
279
312
  cacheTo?: string | null;
280
313
  }): Promise<void>;
281
- async lens(options: {
314
+ lens(options: {
282
315
  cacheFrom?: string | null;
283
316
  cacheTo?: string | null;
284
317
  }): Promise<void>;
285
- async commit(ref: string, options?: {
318
+ commit(ref: string, options?: {
286
319
  parentCommit?: string | null;
287
320
  commitMessage?: string | null;
288
321
  }): Promise<string>;
289
322
  }
290
323
 
291
324
  export class Studio {
292
- static async cleanup(): Promise<void>;
293
- static async getHab(): Promise<any>;
294
- static async getDocker(): Promise<Docker>;
295
- static async isEnvironmentStudio(): Promise<boolean>;
296
- static async get(gitDir: string): Promise<Studio>;
325
+ static cleanup(): Promise<void>;
326
+ static getHab(): Promise<any>;
327
+ static isEnvironmentStudio(): Promise<boolean>;
328
+ static get(gitDir: string): Promise<Studio>;
329
+ static execDocker(args: string[], options?: DockerExecOptions): Promise<string>;
297
330
 
298
- constructor(options: { gitDir: string; container: any });
331
+ constructor(options: { gitDir: string; container: StudioContainer });
299
332
 
300
- container: any;
333
+ container: StudioContainer;
301
334
  gitDir: string;
302
335
 
303
336
  isLocal(): boolean;
304
- async habExec(...command: any[]): Promise<string>;
305
- async habPkgExec(pkg: string, bin: string, ...args: any[]): Promise<string>;
306
- async holoExec(...command: any[]): Promise<string>;
307
- async holoLensExec(spec: string): Promise<string>;
308
- async getPackage(query: string, options?: { install?: boolean }): Promise<string | null>;
337
+ habExec(...command: any[]): Promise<string>;
338
+ habPkgExec(pkg: string, bin: string, ...args: any[]): Promise<string>;
339
+ holoExec(...command: any[]): Promise<string>;
340
+ holoLensExec(spec: string): Promise<string>;
341
+ getPackage(query: string, options?: { install?: boolean }): Promise<string | null>;
309
342
  }
310
343
 
311
344
  export class Mapping extends Configurable {
@@ -319,13 +352,17 @@ declare module 'hologit' {
319
352
  getConfigPath(): string;
320
353
  }
321
354
 
322
- export class SpecObject extends BlobObject {
323
- static async write(repo: Repo, kind: string, data: any): Promise<{
355
+ export class SpecObject {
356
+ constructor(repo: Repo, options: GitObjectOptions);
357
+
358
+ repo: Repo;
359
+ hash: string;
360
+ isSpec: boolean;
361
+
362
+ static write(repo: Repo, kind: string, data: any): Promise<{
324
363
  hash: string;
325
364
  ref: string;
326
365
  }>;
327
366
  static buildRef(kind: string, hash: string): string;
328
-
329
- isSpec: boolean;
330
367
  }
331
368
  }
package/lib/Lens.js CHANGED
@@ -44,12 +44,10 @@ class Lens extends Configurable {
44
44
  const config = await super.getConfig();
45
45
 
46
46
  // process lens configuration
47
- if (!config.package) {
48
- throw new Error(`hololens has no package defined: ${this.name}`);
47
+ if (config.package) {
48
+ config.command = config.command || 'lens-tree {{ input }}';
49
49
  }
50
50
 
51
- config.command = config.command || 'lens-tree {{ input }}';
52
-
53
51
  if (config.before) {
54
52
  config.before =
55
53
  typeof config.before == 'string'
@@ -103,7 +101,62 @@ class Lens extends Configurable {
103
101
  async buildSpec (inputTree) {
104
102
  const config = await this.getCachedConfig();
105
103
 
104
+ if (config.container) {
105
+ return this.buildSpecForContainer(inputTree, config);
106
+ } else if (config.package) {
107
+ return this.buildSpecForHabitatPackage(inputTree, config);
108
+ } else {
109
+ throw new Error(`hololens has no package or container defined: ${this.name}`);
110
+ }
111
+ }
112
+
113
+ async buildSpecForContainer (inputTree, config) {
114
+ const { container: containerQuery } = config;
115
+
116
+ // check if image exists locally first
117
+ let imageHash;
118
+ try {
119
+ const inspectOutput = await Studio.execDocker(['inspect', containerQuery]);
120
+ const imageInfo = JSON.parse(inspectOutput)[0];
121
+ imageHash = imageInfo.Id;
122
+ logger.info(`found local image: ${containerQuery}@${imageHash}`);
123
+ } catch (err) {
124
+ // image doesn't exist locally or can't be inspected, try pulling
125
+ logger.info(`pulling image: ${containerQuery}`);
126
+
127
+ try {
128
+ await Studio.execDocker(['pull', containerQuery], { $relayStdout: true });
129
+ const inspectOutput = await Studio.execDocker(['inspect', containerQuery]);
130
+ const imageInfo = JSON.parse(inspectOutput)[0];
131
+ imageHash = imageInfo.Id;
132
+ } catch (err) {
133
+ throw new Error(`failed to pull container image ${containerQuery}: ${err.message}`);
134
+ }
135
+ }
136
+
137
+ if (!imageHash) {
138
+ throw new Error(`failed to get hash for container image ${containerQuery}`);
139
+ }
140
+
141
+ // build spec
142
+ const data = {
143
+ ...config,
144
+ container: `${containerQuery.replace(/:.*$/, '')}@${imageHash}`,
145
+ input: await inputTree.write(),
146
+ output: null,
147
+ before: null,
148
+ after: null
149
+ };
106
150
 
151
+ // write spec and return packet
152
+ return {
153
+ ...await SpecObject.write(this.workspace.getRepo(), 'lens', data),
154
+ data,
155
+ type: 'container'
156
+ };
157
+ }
158
+
159
+ async buildSpecForHabitatPackage (inputTree, config) {
107
160
  // determine current package version
108
161
  const { package: packageQuery } = config;
109
162
  const [pkgOrigin, pkgName, pkgVersion, pkgBuild] = packageQuery.split('/');
@@ -163,14 +216,6 @@ class Lens extends Configurable {
163
216
  }
164
217
 
165
218
 
166
- // old studio method that might be useful as fallback/debug option
167
- // const setupOutput = await studio.exec('hab', 'pkg', 'install', 'core/hab-plan-build');
168
- // const originOutput = await studio.exec('hab', 'origin', 'key', 'generate', 'holo');
169
- // const buildOutput = await studio.habPkgExec('core/hab-plan-build', 'hab-plan-build', '/src/lenses/compass');
170
- // const studio = await Studio.get(this.workspace.getRepo().gitDir);
171
- // let packageIdent = await studio.getPackage(packageQuery);
172
-
173
-
174
219
  // build spec
175
220
  const data = {
176
221
  ...config,
@@ -185,21 +230,21 @@ class Lens extends Configurable {
185
230
  // write spec and return packet
186
231
  return {
187
232
  ...await SpecObject.write(this.workspace.getRepo(), 'lens', data),
188
- data
233
+ data,
234
+ type: 'habitat'
189
235
  };
190
236
  }
191
237
 
192
- async executeSpec (specHash, options) {
193
- return Lens.executeSpec(specHash, {...options, repo: this.workspace.getRepo()});
238
+ async executeSpec (specType, specHash, options) {
239
+ return Lens.executeSpec(specType, specHash, {...options, repo: this.workspace.getRepo()});
194
240
  }
195
241
 
196
- static async executeSpec (specHash, { refresh=false, save=true, repo=null, cacheFrom=null, cacheTo=null }) {
242
+ static async executeSpec (specType, specHash, options) {
243
+ const { refresh=false, cacheFrom=null, cacheTo=null, save=true } = options;
197
244
 
198
- // load holorepo
199
- if (!repo) {
200
- repo = await Repo.getFromEnvironment();
201
- }
202
245
 
246
+ // load holorepo
247
+ const repo = options.repo || await Repo.getFromEnvironment();
203
248
  const git = await repo.getGit();
204
249
 
205
250
 
@@ -228,9 +273,161 @@ class Lens extends Configurable {
228
273
  }
229
274
 
230
275
 
231
- // ensure the rest runs inside a studio environment
276
+ // execute lens in container or with habitat package:
277
+ let lensedTreeHash;
278
+ if (specType == 'container') {
279
+ lensedTreeHash = await Lens.executeSpecForContainer(repo, specHash);
280
+ } else if (specType == 'habitat') {
281
+ lensedTreeHash = await Lens.executeSpecForHabitatPackage(repo, specHash);
282
+ }
283
+
284
+ // save ref to accelerate next projection
285
+ if (save) {
286
+ await git.updateRef(specRef, lensedTreeHash);
287
+
288
+ if (cacheTo) {
289
+ await _cacheResultTo(repo, specRef, cacheTo);
290
+ }
291
+ }
292
+
293
+ return lensedTreeHash;
294
+ }
295
+
296
+ static async executeSpecForContainer (repo, specHash) {
297
+ const git = await repo.getGit();
298
+
299
+ // read and parse spec file
300
+ const specToml = await git.catFile({ p: true }, specHash);
301
+ const {
302
+ holospec: {
303
+ lens: spec
304
+ }
305
+ } = TOML.parse(specToml);
306
+
307
+ // write commit with input tree and spec content
308
+ const commitHash = await git.commitTree(spec.input, {
309
+ p: [],
310
+ m: specToml
311
+ });
312
+
313
+ // extract repository and hash from container string
314
+ const containerMatch = spec.container.match(/^.+@sha256:([a-f0-9]{64})$/);
315
+ if (!containerMatch) {
316
+ throw new Error(`Invalid container format: ${spec.container}`);
317
+ }
318
+ const [, sha256Hash] = containerMatch;
319
+
320
+ // create and start container
321
+ const persistentDebugContainer = process.env.HOLO_DEBUG_PERSIST_CONTAINER;
322
+ let containerId;
323
+ try {
324
+ if (persistentDebugContainer) {
325
+ try {
326
+ const containerInfo = await Studio.execDocker(['inspect', persistentDebugContainer]);
327
+ const containerState = JSON.parse(containerInfo)[0].State;
328
+
329
+ if (containerState.Running) {
330
+ logger.info(`Found running debug container: ${persistentDebugContainer}`);
331
+ containerId = persistentDebugContainer;
332
+ }
333
+ } catch (error) {
334
+ containerId = null;
335
+ }
336
+ }
337
+
338
+ // create container
339
+ if (!containerId) {
340
+ containerId = await Studio.execDocker([
341
+ 'create',
342
+ '-p', '9000:9000',
343
+ ...(persistentDebugContainer ? ['--name', persistentDebugContainer] : []),
344
+ ...(process.env.DEBUG ? ['-e', 'DEBUG=1'] : []),
345
+ sha256Hash
346
+ ]);
347
+ containerId = containerId.trim();
348
+
349
+ logger.info('starting container');
350
+ await Studio.execDocker(['start', containerId]);
351
+ }
352
+
353
+ // wait for port 9000 to be available
354
+ let attempts = 0;
355
+ const maxAttempts = 30;
356
+ const waitTime = 1000; // 1 second
357
+
358
+ while (attempts < maxAttempts) {
359
+ try {
360
+ const containerInfo = await Studio.execDocker(['inspect', containerId]);
361
+ const containerState = JSON.parse(containerInfo)[0].State;
362
+
363
+ if (containerState.Running) {
364
+ // check if port 9000 is listening
365
+ try {
366
+ await Studio.execDocker([
367
+ 'exec',
368
+ containerId,
369
+ 'nc',
370
+ '-z',
371
+ 'localhost',
372
+ '9000'
373
+ ]);
374
+ break;
375
+ } catch (err) {
376
+ // ignore error and continue waiting
377
+ }
378
+ }
379
+ } catch (err) {
380
+ // ignore error and continue waiting
381
+ }
382
+
383
+ await new Promise(resolve => setTimeout(resolve, waitTime));
384
+ attempts++;
385
+ }
386
+
387
+ if (attempts >= maxAttempts) {
388
+ throw new Error('Timeout waiting for git server to be ready');
389
+ }
390
+
391
+ // push commit to git server
392
+ logger.info(`pushing and executing job: ${commitHash}`);
393
+ await git.push(`http://localhost:9000/`, `${commitHash}:refs/heads/lens-input`, {
394
+ force: true,
395
+ $wait: true,
396
+ $onStderr: (line) => process.stderr.write(`\x1b[90m${line}\x1b[0m\n`)
397
+ });
398
+
399
+ // fetch and verify output commit
400
+ const outputRef = `refs/lens-jobs/${specHash}`;
401
+ logger.info('fetching result');
402
+ await git.fetch('http://localhost:9000/', `+refs/heads/lens-input:${outputRef}`);
403
+
404
+ // verify the output commit's parent matches our input commit
405
+ const outputParent = await git.revParse(`${outputRef}^`);
406
+ if (outputParent !== commitHash) {
407
+ throw new Error(`Output commit parent ${outputParent} does not match input commit ${commitHash}`);
408
+ }
409
+
410
+ return await git.getTreeHash(outputRef);
411
+
412
+ } finally {
413
+ // cleanup
414
+ if (containerId && !persistentDebugContainer) {
415
+ try {
416
+ await Studio.execDocker(['stop', containerId]);
417
+ await Studio.execDocker(['rm', containerId]);
418
+ } catch (err) {
419
+ logger.warn(`Failed to cleanup container: ${err.message}`);
420
+ }
421
+ }
422
+ }
423
+ }
424
+
425
+ static async executeSpecForHabitatPackage (repo, specHash) {
426
+ const git = await repo.getGit();
427
+
232
428
  let lensedTreeHash;
233
429
 
430
+ // ensure the rest runs inside a studio environment
234
431
  if (!await Studio.isEnvironmentStudio()) {
235
432
  const studio = await Studio.get(repo.gitDir);
236
433
  lensedTreeHash = await studio.holoLensExec(specHash);
@@ -307,16 +504,6 @@ class Lens extends Configurable {
307
504
  }
308
505
 
309
506
 
310
- // save ref to accelerate next projection
311
- if (save) {
312
- await git.updateRef(specRef, lensedTreeHash);
313
-
314
- if (cacheTo) {
315
- await _cacheResultTo(repo, specRef, cacheTo);
316
- }
317
- }
318
-
319
-
320
507
  // return tree hash
321
508
  return lensedTreeHash;
322
509
  }
package/lib/Projection.js CHANGED
@@ -162,11 +162,11 @@ class Projection {
162
162
 
163
163
  // build tree of matching files to input to lens
164
164
  logger.info(`building input tree for lens ${lens.name} from ${inputRoot == '.' ? '' : (path.join(inputRoot, '.')+'/')}{${inputFiles}}`);
165
- const { hash: specHash } = await lens.buildSpec(await lens.buildInputTree(this.output.root));
165
+ const { hash: specHash, type: specType } = await lens.buildSpec(await lens.buildInputTree(this.output.root));
166
166
 
167
167
 
168
168
  // check for existing output tree
169
- const outputTreeHash = await lens.executeSpec(specHash, { cacheFrom, cacheTo });
169
+ const outputTreeHash = await lens.executeSpec(specType, specHash, { cacheFrom, cacheTo });
170
170
 
171
171
 
172
172
  // verify output