@liquidmetal-ai/drizzle 0.0.4 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.changeset/slow-guests-stare.md +7 -0
- package/.changeset/tasty-tigers-wash.md +6 -0
- package/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-lint.log +1 -1
- package/.turbo/turbo-test.log +211 -0
- package/dist/appify/build.d.ts +1 -0
- package/dist/appify/build.d.ts.map +1 -1
- package/dist/appify/build.js +4 -3
- package/dist/appify/build.test.js +1 -3
- package/dist/appify/index.test.js +2 -2
- package/dist/appify/parse.test.js +0 -1
- package/dist/appify/validate.d.ts.map +1 -1
- package/dist/appify/validate.js +34 -7
- package/dist/appify/validate.test.js +119 -26
- package/dist/codestore.d.ts +40 -0
- package/dist/codestore.d.ts.map +1 -1
- package/dist/codestore.js +15 -0
- package/dist/liquidmetal/v1alpha1/catalog_connect.d.ts +12 -12
- package/dist/liquidmetal/v1alpha1/catalog_connect.d.ts.map +1 -1
- package/dist/liquidmetal/v1alpha1/catalog_connect.js +12 -12
- package/dist/liquidmetal/v1alpha1/catalog_pb.d.ts +119 -77
- package/dist/liquidmetal/v1alpha1/catalog_pb.d.ts.map +1 -1
- package/dist/liquidmetal/v1alpha1/catalog_pb.js +177 -107
- package/dist/raindrop/index.d.ts +2 -0
- package/dist/raindrop/index.d.ts.map +1 -0
- package/dist/raindrop/index.js +4 -0
- package/dist/raindrop/index.test.d.ts +2 -0
- package/dist/raindrop/index.test.d.ts.map +1 -0
- package/dist/raindrop/index.test.js +5 -0
- package/dist/unsafe/codestore.d.ts +5 -0
- package/dist/unsafe/codestore.d.ts.map +1 -1
- package/dist/unsafe/codestore.js +5 -1
- package/dist/unsafe/framework.d.ts +11 -0
- package/dist/unsafe/framework.d.ts.map +1 -0
- package/dist/unsafe/framework.js +96 -0
- package/dist/unsafe/framework.test.d.ts +2 -0
- package/dist/unsafe/framework.test.d.ts.map +1 -0
- package/dist/unsafe/framework.test.js +175 -0
- package/eslint.config.mjs +1 -2
- package/package.json +1 -1
- package/src/appify/build.test.ts +1 -3
- package/src/appify/build.ts +4 -3
- package/src/appify/index.test.ts +2 -2
- package/src/appify/parse.test.ts +0 -1
- package/src/appify/validate.test.ts +123 -26
- package/src/appify/validate.ts +39 -7
- package/src/codestore.ts +40 -1
- package/src/liquidmetal/v1alpha1/catalog_connect.ts +12 -12
- package/src/liquidmetal/v1alpha1/catalog_pb.ts +213 -127
- package/src/raindrop/index.test.ts +6 -0
- package/src/raindrop/index.ts +4 -0
- package/src/unsafe/codestore.ts +7 -3
- package/src/unsafe/framework.test.ts +205 -0
- package/src/unsafe/framework.ts +113 -0
- package/tsconfig.json +3 -9
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { exec } from 'node:child_process';
|
|
2
|
+
import { mkdir, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
6
|
+
import { getPackageVersion } from './framework.js';
|
|
7
|
+
vi.mock('node:child_process', () => ({
|
|
8
|
+
exec: vi.fn(),
|
|
9
|
+
}));
|
|
10
|
+
describe('getPackageVersion', () => {
|
|
11
|
+
let tempDir;
|
|
12
|
+
beforeEach(async () => {
|
|
13
|
+
vi.resetAllMocks();
|
|
14
|
+
// Create a new temp directory for each test
|
|
15
|
+
tempDir = join(tmpdir(), `test-${Date.now()}`);
|
|
16
|
+
await mkdir(tempDir, { recursive: true });
|
|
17
|
+
// Mock process.cwd() to return our temp directory
|
|
18
|
+
vi.spyOn(process, 'cwd').mockReturnValue(tempDir);
|
|
19
|
+
});
|
|
20
|
+
it('should detect npm package version', async () => {
|
|
21
|
+
// Create package-lock.json to indicate npm
|
|
22
|
+
await writeFile(join(tempDir, 'package-lock.json'), JSON.stringify({ name: 'test' }));
|
|
23
|
+
// Mock npm list command output
|
|
24
|
+
vi.mocked(exec).mockImplementation((command, callback) => {
|
|
25
|
+
if (command.includes('npm list')) {
|
|
26
|
+
callback(null, {
|
|
27
|
+
stdout: JSON.stringify({
|
|
28
|
+
dependencies: {
|
|
29
|
+
'test-package': {
|
|
30
|
+
version: '1.2.3',
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
}),
|
|
34
|
+
stderr: '',
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
38
|
+
return {};
|
|
39
|
+
});
|
|
40
|
+
const version = await getPackageVersion('test-package');
|
|
41
|
+
expect(version).toBe('1.2.3');
|
|
42
|
+
});
|
|
43
|
+
it('should detect yarn package version', async () => {
|
|
44
|
+
// Create yarn.lock to indicate yarn
|
|
45
|
+
await writeFile(join(tempDir, 'yarn.lock'), 'yarn lock contents');
|
|
46
|
+
// Mock yarn list command output
|
|
47
|
+
vi.mocked(exec).mockImplementation((command, callback) => {
|
|
48
|
+
if (command.includes('yarn list')) {
|
|
49
|
+
callback(null, {
|
|
50
|
+
stdout: JSON.stringify({
|
|
51
|
+
data: {
|
|
52
|
+
trees: [
|
|
53
|
+
{
|
|
54
|
+
name: 'test-package',
|
|
55
|
+
version: '2.3.4',
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
},
|
|
59
|
+
}),
|
|
60
|
+
stderr: '',
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
64
|
+
return {};
|
|
65
|
+
});
|
|
66
|
+
const version = await getPackageVersion('test-package');
|
|
67
|
+
expect(version).toBe('2.3.4');
|
|
68
|
+
});
|
|
69
|
+
it('should detect pnpm package version', async () => {
|
|
70
|
+
// Create pnpm-lock.yaml to indicate pnpm
|
|
71
|
+
await writeFile(join(tempDir, 'pnpm-lock.yaml'), 'pnpm lock contents');
|
|
72
|
+
// Mock pnpm list command output
|
|
73
|
+
vi.mocked(exec).mockImplementation((command, callback) => {
|
|
74
|
+
if (command.includes('pnpm list')) {
|
|
75
|
+
callback(null, {
|
|
76
|
+
stdout: JSON.stringify({
|
|
77
|
+
dependencies: {
|
|
78
|
+
'test-package': {
|
|
79
|
+
version: '3.4.5',
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
}),
|
|
83
|
+
stderr: '',
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
87
|
+
return {};
|
|
88
|
+
});
|
|
89
|
+
const version = await getPackageVersion('test-package');
|
|
90
|
+
expect(version).toBe('3.4.5');
|
|
91
|
+
});
|
|
92
|
+
it('should handle nested dependencies', async () => {
|
|
93
|
+
await writeFile(join(tempDir, 'package-lock.json'), JSON.stringify({ name: 'test' }));
|
|
94
|
+
vi.mocked(exec).mockImplementation((command, callback) => {
|
|
95
|
+
if (command.includes('npm list')) {
|
|
96
|
+
callback(null, {
|
|
97
|
+
stdout: JSON.stringify({
|
|
98
|
+
dependencies: {
|
|
99
|
+
'parent-package': {
|
|
100
|
+
version: '1.0.0',
|
|
101
|
+
dependencies: {
|
|
102
|
+
'test-package': {
|
|
103
|
+
version: '4.5.6',
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
}),
|
|
109
|
+
stderr: '',
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
113
|
+
return {};
|
|
114
|
+
});
|
|
115
|
+
const version = await getPackageVersion('test-package');
|
|
116
|
+
expect(version).toBe('4.5.6');
|
|
117
|
+
});
|
|
118
|
+
it('should return null when package is not found', async () => {
|
|
119
|
+
await writeFile(join(tempDir, 'package-lock.json'), JSON.stringify({ name: 'test' }));
|
|
120
|
+
vi.mocked(exec).mockImplementation((command, callback) => {
|
|
121
|
+
if (command.includes('npm list')) {
|
|
122
|
+
callback(null, {
|
|
123
|
+
stdout: JSON.stringify({
|
|
124
|
+
dependencies: {},
|
|
125
|
+
}),
|
|
126
|
+
stderr: '',
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
130
|
+
return {};
|
|
131
|
+
});
|
|
132
|
+
const version = await getPackageVersion('non-existent-package');
|
|
133
|
+
expect(version).toBeNull();
|
|
134
|
+
});
|
|
135
|
+
it('should search parent directories for package manager files', async () => {
|
|
136
|
+
// Create a nested directory structure
|
|
137
|
+
const nestedDir = join(tempDir, 'nested', 'deeper');
|
|
138
|
+
await mkdir(nestedDir, { recursive: true });
|
|
139
|
+
// Create package-lock.json in parent directory
|
|
140
|
+
await writeFile(join(tempDir, 'package-lock.json'), JSON.stringify({ name: 'test' }));
|
|
141
|
+
// Set cwd to nested directory
|
|
142
|
+
vi.spyOn(process, 'cwd').mockReturnValue(nestedDir);
|
|
143
|
+
vi.mocked(exec).mockImplementation((command, callback) => {
|
|
144
|
+
if (command.includes('npm list')) {
|
|
145
|
+
callback(null, {
|
|
146
|
+
stdout: JSON.stringify({
|
|
147
|
+
dependencies: {
|
|
148
|
+
'test-package': {
|
|
149
|
+
version: '5.6.7',
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
}),
|
|
153
|
+
stderr: '',
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
157
|
+
return {};
|
|
158
|
+
});
|
|
159
|
+
const version = await getPackageVersion('test-package');
|
|
160
|
+
expect(version).toBe('5.6.7');
|
|
161
|
+
});
|
|
162
|
+
it('should handle command execution errors', async () => {
|
|
163
|
+
await writeFile(join(tempDir, 'package-lock.json'), JSON.stringify({ name: 'test' }));
|
|
164
|
+
vi.mocked(exec).mockImplementation((_command, callback) => {
|
|
165
|
+
callback(new Error('Command failed'), {
|
|
166
|
+
stdout: '',
|
|
167
|
+
stderr: 'failure',
|
|
168
|
+
});
|
|
169
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
170
|
+
return {};
|
|
171
|
+
});
|
|
172
|
+
const version = await getPackageVersion('test-package');
|
|
173
|
+
expect(version).toBeNull();
|
|
174
|
+
});
|
|
175
|
+
});
|
package/eslint.config.mjs
CHANGED
package/package.json
CHANGED
package/src/appify/build.test.ts
CHANGED
package/src/appify/build.ts
CHANGED
|
@@ -345,9 +345,6 @@ function buildSqlDatabase(stanza: StanzaNode): [SqlDatabase, ConfigError[]] {
|
|
|
345
345
|
case 'visibility':
|
|
346
346
|
buildAssignment(sqlDatabase, 'visibility', 'string', child, errors);
|
|
347
347
|
break;
|
|
348
|
-
case 'schema':
|
|
349
|
-
buildAssignment(sqlDatabase, 'schema', 'string', child, errors);
|
|
350
|
-
break;
|
|
351
348
|
default:
|
|
352
349
|
errors.push({ message: 'unexpected assignment', ...child });
|
|
353
350
|
}
|
|
@@ -543,6 +540,9 @@ function buildDomain(stanza: StanzaNode): [Domain, ConfigError[]] {
|
|
|
543
540
|
for (const child of stanza.block?.children ?? []) {
|
|
544
541
|
if (child.type === 'assignment') {
|
|
545
542
|
switch (child.key.value) {
|
|
543
|
+
case 'cname':
|
|
544
|
+
buildAssignment(domain, 'cname', 'string', child, errors);
|
|
545
|
+
break;
|
|
546
546
|
case 'fqdn':
|
|
547
547
|
buildAssignment(domain, 'fqdn', 'string', child, errors);
|
|
548
548
|
break;
|
|
@@ -703,6 +703,7 @@ export class Route {
|
|
|
703
703
|
|
|
704
704
|
export class Domain {
|
|
705
705
|
obj: ConfigObject;
|
|
706
|
+
cname?: TokenString;
|
|
706
707
|
fqdn?: TokenString;
|
|
707
708
|
|
|
708
709
|
constructor(obj: ConfigObject) {
|
package/src/appify/index.test.ts
CHANGED
package/src/appify/parse.test.ts
CHANGED
|
@@ -55,32 +55,129 @@ application "my-app" {
|
|
|
55
55
|
]);
|
|
56
56
|
});
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
58
|
+
test('domainValidator ensures domain.fqdn is valid', async () => {
|
|
59
|
+
const manifest = `
|
|
60
|
+
application "my-app" {
|
|
61
|
+
service "my-service" {
|
|
62
|
+
domain {
|
|
63
|
+
fqdn = "not-valid.com_foo"
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
`;
|
|
68
|
+
const tokenizer = new Tokenizer(manifest);
|
|
69
|
+
const parser = new Parser(tokenizer);
|
|
70
|
+
const parsedManifest = parser.parse();
|
|
71
|
+
const [builtApps] = buildManifest(parsedManifest);
|
|
72
|
+
const validateErrors = await validate(builtApps, VALIDATORS);
|
|
73
|
+
expect(validateErrors).toMatchObject([
|
|
74
|
+
{
|
|
75
|
+
message: 'domain "not-valid.com_foo" is an invalid domain name',
|
|
76
|
+
line: 4,
|
|
77
|
+
column: 5,
|
|
78
|
+
start: 53,
|
|
79
|
+
end: 100,
|
|
80
|
+
severity: 'error',
|
|
81
|
+
},
|
|
82
|
+
]);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('domainValidator ensures domain.fqdn is valid but ignores variable interpolation', async () => {
|
|
86
|
+
const manifest = `
|
|
87
|
+
application "my-app" {
|
|
88
|
+
service "my-service" {
|
|
89
|
+
domain {
|
|
90
|
+
fqdn = "$\{MY_SERVICES_EXAMPLE_URL}"
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
`;
|
|
95
|
+
const tokenizer = new Tokenizer(manifest);
|
|
96
|
+
const parser = new Parser(tokenizer);
|
|
97
|
+
const parsedManifest = parser.parse();
|
|
98
|
+
const [builtApps] = buildManifest(parsedManifest);
|
|
99
|
+
const validateErrors = await validate(builtApps, VALIDATORS);
|
|
100
|
+
expect(validateErrors).toEqual([]);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test('domainValidator ensures either a cname or fqdn present', async () => {
|
|
104
|
+
const manifest = `
|
|
105
|
+
application "my-app" {
|
|
106
|
+
service "my-service" {
|
|
107
|
+
domain {}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
`;
|
|
111
|
+
const tokenizer = new Tokenizer(manifest);
|
|
112
|
+
const parser = new Parser(tokenizer);
|
|
113
|
+
const parsedManifest = parser.parse();
|
|
114
|
+
const [builtApps] = buildManifest(parsedManifest);
|
|
115
|
+
const validateErrors = await validate(builtApps, VALIDATORS);
|
|
116
|
+
expect(validateErrors).toMatchObject([
|
|
117
|
+
{
|
|
118
|
+
message: 'domain must have either a cname or fqdn attribute',
|
|
119
|
+
line: 4,
|
|
120
|
+
column: 5,
|
|
121
|
+
start: 53,
|
|
122
|
+
end: 62,
|
|
123
|
+
severity: 'error',
|
|
124
|
+
},
|
|
125
|
+
]);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test('domainValidator ensures ensures domain.cname is valid', async () => {
|
|
129
|
+
const manifest = `
|
|
130
|
+
application "my-app" {
|
|
131
|
+
service "my-service" {
|
|
132
|
+
domain {
|
|
133
|
+
cname = "my_cname"
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
`;
|
|
138
|
+
const tokenizer = new Tokenizer(manifest);
|
|
139
|
+
const parser = new Parser(tokenizer);
|
|
140
|
+
const parsedManifest = parser.parse();
|
|
141
|
+
const [builtApps] = buildManifest(parsedManifest);
|
|
142
|
+
const validateErrors = await validate(builtApps, VALIDATORS);
|
|
143
|
+
expect(validateErrors).toMatchObject([
|
|
144
|
+
{
|
|
145
|
+
message: 'domain "my_cname" is an invalid subdomain name',
|
|
146
|
+
line: 4,
|
|
147
|
+
column: 5,
|
|
148
|
+
start: 53,
|
|
149
|
+
end: 92,
|
|
150
|
+
severity: 'error',
|
|
151
|
+
},
|
|
152
|
+
]);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test('domainValidator validates domain.cname does not exceed the maximum length', async () => {
|
|
156
|
+
const manifest = `
|
|
157
|
+
application "my-app" {
|
|
158
|
+
service "my-service" {
|
|
159
|
+
domain {
|
|
160
|
+
cname = "really-long-cname-that-is-invalid-because-it-is-too-long"
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
`;
|
|
165
|
+
const tokenizer = new Tokenizer(manifest);
|
|
166
|
+
const parser = new Parser(tokenizer);
|
|
167
|
+
const parsedManifest = parser.parse();
|
|
168
|
+
const [builtApps] = buildManifest(parsedManifest);
|
|
169
|
+
const validateErrors = await validate(builtApps, VALIDATORS);
|
|
170
|
+
expect(validateErrors).toMatchObject([
|
|
171
|
+
{
|
|
172
|
+
message: 'domain.cname undefined is too long, maximum 26 characters',
|
|
173
|
+
line: 4,
|
|
174
|
+
column: 5,
|
|
175
|
+
start: 53,
|
|
176
|
+
end: 140,
|
|
177
|
+
severity: 'error',
|
|
178
|
+
},
|
|
179
|
+
]);
|
|
180
|
+
});
|
|
84
181
|
|
|
85
182
|
test('visibilityValidator', async () => {
|
|
86
183
|
const manifest = `
|
package/src/appify/validate.ts
CHANGED
|
@@ -203,23 +203,54 @@ const bindingValueValidator: Validator = {
|
|
|
203
203
|
},
|
|
204
204
|
};
|
|
205
205
|
|
|
206
|
-
|
|
207
|
-
const
|
|
206
|
+
const VALIDATE_DOMAIN_PATTERN = /^[a-z0-9]+([-.]{1}[a-z0-9]+)*\.[a-z]{2,6}$/;
|
|
207
|
+
const FQDN_MAX_LENGTH = 63;
|
|
208
|
+
const CNAME_MAX_LENGTH = 63 - `.01abcdefghijklmnopqrstuvwx.lmapp.run`.length;
|
|
209
|
+
|
|
210
|
+
const domainValidator: Validator = {
|
|
208
211
|
onDomain: async (app: Application, domain: Domain): Promise<ValidationError[]> => {
|
|
209
212
|
const errors: ValidationError[] = [];
|
|
210
|
-
if (domain.fqdn === undefined) {
|
|
213
|
+
if (domain.cname == undefined && domain.fqdn === undefined) {
|
|
211
214
|
errors.push({
|
|
212
|
-
message: 'domain must have
|
|
215
|
+
message: 'domain must have either a cname or fqdn attribute',
|
|
213
216
|
severity: 'error',
|
|
214
217
|
...domain.obj,
|
|
215
218
|
});
|
|
216
|
-
|
|
219
|
+
return errors;
|
|
220
|
+
}
|
|
221
|
+
if (
|
|
222
|
+
domain.fqdn !== undefined &&
|
|
223
|
+
valueOf(domain.fqdn).indexOf('${') === -1 &&
|
|
224
|
+
!VALIDATE_DOMAIN_PATTERN.test(valueOf(domain.fqdn))
|
|
225
|
+
) {
|
|
217
226
|
errors.push({
|
|
218
227
|
message: `domain ${domain.fqdn.value} is an invalid domain name`,
|
|
219
|
-
severity: '
|
|
220
|
-
...domain.
|
|
228
|
+
severity: 'error',
|
|
229
|
+
...domain.obj,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
if (domain.cname !== undefined && !VALIDATE_DOMAIN_PATTERN.test(`${valueOf(domain.cname)}.example.com`)) {
|
|
233
|
+
errors.push({
|
|
234
|
+
message: `domain ${domain.cname.value} is an invalid subdomain name`,
|
|
235
|
+
severity: 'error',
|
|
236
|
+
...domain.obj,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
if (domain.cname !== undefined && valueOf(domain.cname).length > CNAME_MAX_LENGTH) {
|
|
240
|
+
errors.push({
|
|
241
|
+
message: `domain.cname ${domain.fqdn} is too long, maximum ${CNAME_MAX_LENGTH} characters`,
|
|
242
|
+
severity: 'error',
|
|
243
|
+
...domain.obj,
|
|
221
244
|
});
|
|
222
245
|
}
|
|
246
|
+
if (domain.fqdn !== undefined && valueOf(domain.fqdn).length > FQDN_MAX_LENGTH) {
|
|
247
|
+
errors.push({
|
|
248
|
+
message: `domain.fqdn ${domain.fqdn} is too long, maximum ${FQDN_MAX_LENGTH} characters`,
|
|
249
|
+
severity: 'error',
|
|
250
|
+
...domain.obj,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
223
254
|
return errors;
|
|
224
255
|
},
|
|
225
256
|
};
|
|
@@ -438,6 +469,7 @@ const duplicateModuleValidator: Validator = {
|
|
|
438
469
|
export const VALIDATORS: Validator[] = [
|
|
439
470
|
bindingNameValidator,
|
|
440
471
|
bindingValueValidator,
|
|
472
|
+
domainValidator,
|
|
441
473
|
envValidator,
|
|
442
474
|
observerSourceValidator,
|
|
443
475
|
visibilityValidator,
|
package/src/codestore.ts
CHANGED
|
@@ -1,16 +1,28 @@
|
|
|
1
1
|
import crypto from 'crypto';
|
|
2
2
|
import JSZip from 'jszip';
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
/**
|
|
5
|
+
* CodeStore represents a collection of code bundles mapped by handler names
|
|
6
|
+
* Used to organize and manage multiple code bundles in a single namespace
|
|
7
|
+
*/
|
|
5
8
|
export interface CodeStore {
|
|
6
9
|
[key: string]: Bundle;
|
|
7
10
|
}
|
|
8
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Statistics about a file in a bundle
|
|
14
|
+
* Includes size and cryptographic hash information
|
|
15
|
+
*/
|
|
9
16
|
export interface Stat {
|
|
10
17
|
sizeBytes: number;
|
|
11
18
|
sha256Hex: string;
|
|
12
19
|
}
|
|
13
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Interface for read-only access to a bundle of files
|
|
23
|
+
* Provides methods to list, read, and get statistics about files
|
|
24
|
+
* Also supports async iteration over files
|
|
25
|
+
*/
|
|
14
26
|
export interface ReadableBundle {
|
|
15
27
|
list(): Promise<string[]>;
|
|
16
28
|
read(name: string): Promise<Buffer>;
|
|
@@ -19,19 +31,36 @@ export interface ReadableBundle {
|
|
|
19
31
|
hash(): Promise<string>;
|
|
20
32
|
}
|
|
21
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Interface for write operations on a bundle of files
|
|
36
|
+
* Provides methods to write and delete files
|
|
37
|
+
*/
|
|
22
38
|
export interface WritableBundle {
|
|
23
39
|
write(name: string, content: Buffer): Promise<void>;
|
|
24
40
|
delete(name: string): Promise<void>;
|
|
25
41
|
}
|
|
26
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Complete interface for a bundle of files
|
|
45
|
+
* Combines both read and write operations
|
|
46
|
+
*/
|
|
27
47
|
export type Bundle = ReadableBundle & WritableBundle;
|
|
28
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Represents a single file within a bundle
|
|
51
|
+
* Provides methods to read the file's contents and get its statistics
|
|
52
|
+
*/
|
|
29
53
|
export interface File {
|
|
30
54
|
name: string;
|
|
31
55
|
read(): Promise<Buffer>;
|
|
32
56
|
stat(): Promise<Stat>;
|
|
33
57
|
}
|
|
34
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Base implementation of ReadableBundle
|
|
61
|
+
* Provides default implementations for stat(), hash() and async iteration
|
|
62
|
+
* Concrete implementations must provide list() and read() methods
|
|
63
|
+
*/
|
|
35
64
|
export abstract class BundleBase implements ReadableBundle {
|
|
36
65
|
abstract list(): Promise<string[]>;
|
|
37
66
|
abstract read(name: string): Promise<Buffer>;
|
|
@@ -73,6 +102,11 @@ export abstract class BundleBase implements ReadableBundle {
|
|
|
73
102
|
}
|
|
74
103
|
}
|
|
75
104
|
|
|
105
|
+
/**
|
|
106
|
+
* Creates a ZIP archive containing all files from a bundle
|
|
107
|
+
* @param bundle The bundle to archive
|
|
108
|
+
* @returns Promise resolving to the ZIP file contents as an ArrayBuffer
|
|
109
|
+
*/
|
|
76
110
|
export async function archive(bundle: ReadableBundle): Promise<ArrayBuffer> {
|
|
77
111
|
const zip = new JSZip();
|
|
78
112
|
for await (const file of bundle) {
|
|
@@ -81,6 +115,11 @@ export async function archive(bundle: ReadableBundle): Promise<ArrayBuffer> {
|
|
|
81
115
|
return await zip.generateAsync({ type: 'arraybuffer' });
|
|
82
116
|
}
|
|
83
117
|
|
|
118
|
+
/**
|
|
119
|
+
* Extracts files from a ZIP archive into a writable bundle
|
|
120
|
+
* @param zipBuffer The ZIP file contents as an ArrayBuffer
|
|
121
|
+
* @param bundle The target bundle to write the extracted files to
|
|
122
|
+
*/
|
|
84
123
|
export async function unarchive(zipBuffer: ArrayBuffer, bundle: WritableBundle): Promise<void> {
|
|
85
124
|
const zip = await JSZip.loadAsync(zipBuffer);
|
|
86
125
|
for (const [, file] of Object.entries(zip.files)) {
|
|
@@ -23,6 +23,18 @@ import { MethodKind } from "@bufbuild/protobuf";
|
|
|
23
23
|
export const CatalogService = {
|
|
24
24
|
typeName: "liquidmetal.v1alpha1.CatalogService",
|
|
25
25
|
methods: {
|
|
26
|
+
/**
|
|
27
|
+
* Bootstrap is a special RPC that is used to bootstrap the system
|
|
28
|
+
* using a one-time token.
|
|
29
|
+
*
|
|
30
|
+
* @generated from rpc liquidmetal.v1alpha1.CatalogService.Bootstrap
|
|
31
|
+
*/
|
|
32
|
+
bootstrap: {
|
|
33
|
+
name: "Bootstrap",
|
|
34
|
+
I: BootstrapRequest,
|
|
35
|
+
O: BootstrapResponse,
|
|
36
|
+
kind: MethodKind.Unary,
|
|
37
|
+
},
|
|
26
38
|
/**
|
|
27
39
|
* @generated from rpc liquidmetal.v1alpha1.CatalogService.Versions
|
|
28
40
|
*/
|
|
@@ -154,18 +166,6 @@ export const CatalogService = {
|
|
|
154
166
|
O: QueryResourcesResponse,
|
|
155
167
|
kind: MethodKind.Unary,
|
|
156
168
|
},
|
|
157
|
-
/**
|
|
158
|
-
* Bootstrap is a special RPC that is used to bootstrap the system
|
|
159
|
-
* using a one-time token.
|
|
160
|
-
*
|
|
161
|
-
* @generated from rpc liquidmetal.v1alpha1.CatalogService.Bootstrap
|
|
162
|
-
*/
|
|
163
|
-
bootstrap: {
|
|
164
|
-
name: "Bootstrap",
|
|
165
|
-
I: BootstrapRequest,
|
|
166
|
-
O: BootstrapResponse,
|
|
167
|
-
kind: MethodKind.Unary,
|
|
168
|
-
},
|
|
169
169
|
}
|
|
170
170
|
} as const;
|
|
171
171
|
|