puls-dev 0.3.6 → 0.3.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -11
- package/dist/bin/install-shell.js +5 -6
- package/dist/bin/puls.js +10 -3
- package/dist/core/config.d.ts +4 -0
- package/dist/core/decorators.d.ts +4 -0
- package/dist/core/decorators.js +2 -0
- package/dist/core/parallel.test.js +4 -3
- package/dist/core/resource.d.ts +2 -1
- package/dist/core/resource.js +4 -2
- package/dist/core/stack.d.ts +4 -0
- package/dist/core/stack.js +8 -8
- package/dist/providers/aws/acm.test.d.ts +1 -0
- package/dist/providers/aws/acm.test.js +167 -0
- package/dist/providers/aws/cloudfront.test.d.ts +1 -0
- package/dist/providers/aws/cloudfront.test.js +170 -0
- package/dist/providers/aws/fargate.test.d.ts +1 -0
- package/dist/providers/aws/fargate.test.js +244 -0
- package/dist/providers/aws/rds.test.d.ts +1 -0
- package/dist/providers/aws/rds.test.js +219 -0
- package/dist/providers/aws/sqs.test.d.ts +1 -0
- package/dist/providers/aws/sqs.test.js +181 -0
- package/dist/providers/cloudflare/api.d.ts +15 -0
- package/dist/providers/cloudflare/api.js +199 -0
- package/dist/providers/cloudflare/index.d.ts +14 -0
- package/dist/providers/cloudflare/index.js +19 -0
- package/dist/providers/cloudflare/kv.d.ts +20 -0
- package/dist/providers/cloudflare/kv.js +69 -0
- package/dist/providers/cloudflare/kv.test.d.ts +1 -0
- package/dist/providers/cloudflare/kv.test.js +134 -0
- package/dist/providers/cloudflare/r2.d.ts +14 -0
- package/dist/providers/cloudflare/r2.js +57 -0
- package/dist/providers/cloudflare/r2.test.d.ts +1 -0
- package/dist/providers/cloudflare/r2.test.js +132 -0
- package/dist/providers/cloudflare/worker.d.ts +28 -0
- package/dist/providers/cloudflare/worker.js +172 -0
- package/dist/providers/cloudflare/worker.test.d.ts +1 -0
- package/dist/providers/cloudflare/worker.test.js +220 -0
- package/dist/providers/cloudflare/zone.d.ts +42 -0
- package/dist/providers/cloudflare/zone.js +280 -0
- package/dist/providers/cloudflare/zone.test.d.ts +1 -0
- package/dist/providers/cloudflare/zone.test.js +284 -0
- package/dist/providers/firebase/auth.test.d.ts +1 -0
- package/dist/providers/firebase/auth.test.js +145 -0
- package/dist/providers/firebase/hosting.test.js +7 -6
- package/dist/providers/firebase/storage.test.d.ts +1 -0
- package/dist/providers/firebase/storage.test.js +148 -0
- package/package.json +6 -2
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { test, describe, beforeEach, afterEach, mock } from 'node:test';
|
|
2
|
+
import assert from 'node:assert';
|
|
3
|
+
import { GoogleAuth } from 'google-auth-library';
|
|
4
|
+
import { FirebaseAuthBuilder } from './auth.js';
|
|
5
|
+
import { Config } from '../../core/config.js';
|
|
6
|
+
describe('FirebaseAuthBuilder Unit Tests', () => {
|
|
7
|
+
let originalFetch;
|
|
8
|
+
let fetchCalls = [];
|
|
9
|
+
let mockResponses = {};
|
|
10
|
+
function matchResponse(method, url) {
|
|
11
|
+
const key = Object.keys(mockResponses)
|
|
12
|
+
.filter((k) => {
|
|
13
|
+
const [m, path] = k.split(' ');
|
|
14
|
+
return method === m && url.includes(path);
|
|
15
|
+
})
|
|
16
|
+
.sort((a, b) => b.split(' ')[1].length - a.split(' ')[1].length)[0];
|
|
17
|
+
return key ? mockResponses[key] : null;
|
|
18
|
+
}
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
Config.set({
|
|
21
|
+
dryRun: false,
|
|
22
|
+
providers: { firebase: { projectId: 'my-project', serviceAccountPath: '/fake/sa.json' } },
|
|
23
|
+
});
|
|
24
|
+
originalFetch = globalThis.fetch;
|
|
25
|
+
fetchCalls = [];
|
|
26
|
+
mockResponses = {};
|
|
27
|
+
globalThis.fetch = async (input, init) => {
|
|
28
|
+
const url = String(input);
|
|
29
|
+
const method = init?.method ?? 'GET';
|
|
30
|
+
let body;
|
|
31
|
+
if (init?.body && typeof init.body === 'string') {
|
|
32
|
+
try {
|
|
33
|
+
body = JSON.parse(init.body);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
body = init.body;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
fetchCalls.push({ url, method, body });
|
|
40
|
+
const resp = matchResponse(method, url);
|
|
41
|
+
if (resp) {
|
|
42
|
+
return {
|
|
43
|
+
ok: resp.status >= 200 && resp.status < 300,
|
|
44
|
+
status: resp.status,
|
|
45
|
+
json: async () => resp.body,
|
|
46
|
+
text: async () => JSON.stringify(resp.body),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
ok: true,
|
|
51
|
+
status: 200,
|
|
52
|
+
json: async () => ({}),
|
|
53
|
+
text: async () => '{}',
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
mock.method(GoogleAuth.prototype, 'getClient', async () => ({
|
|
57
|
+
getAccessToken: async () => ({ token: 'fake-token' }),
|
|
58
|
+
}));
|
|
59
|
+
});
|
|
60
|
+
afterEach(() => {
|
|
61
|
+
globalThis.fetch = originalFetch;
|
|
62
|
+
mock.restoreAll();
|
|
63
|
+
});
|
|
64
|
+
test('performs dry-run without any API write calls', async () => {
|
|
65
|
+
Config.set({
|
|
66
|
+
dryRun: true,
|
|
67
|
+
providers: { firebase: { projectId: 'my-project', serviceAccountPath: '/fake/sa.json' } },
|
|
68
|
+
});
|
|
69
|
+
const builder = new FirebaseAuthBuilder();
|
|
70
|
+
builder.emailPassword().anonymous().phone();
|
|
71
|
+
const result = await builder.deploy();
|
|
72
|
+
assert.ok(result);
|
|
73
|
+
assert.strictEqual(result.project, 'my-project');
|
|
74
|
+
const writeCalls = fetchCalls.filter((c) => c.method !== 'GET');
|
|
75
|
+
assert.strictEqual(writeCalls.length, 0);
|
|
76
|
+
});
|
|
77
|
+
test('configures email/password and anonymous sign-in via PATCH', async () => {
|
|
78
|
+
const builder = new FirebaseAuthBuilder();
|
|
79
|
+
builder.emailPassword({ passwordRequired: false }).anonymous();
|
|
80
|
+
await builder.deploy();
|
|
81
|
+
const patchCall = fetchCalls.find((c) => c.method === 'PATCH' && c.url.includes('/config'));
|
|
82
|
+
assert.ok(patchCall);
|
|
83
|
+
assert.ok(patchCall.url.includes('signIn.email'));
|
|
84
|
+
assert.ok(patchCall.url.includes('signIn.anonymous'));
|
|
85
|
+
assert.strictEqual(patchCall.body.signIn.email.enabled, true);
|
|
86
|
+
assert.strictEqual(patchCall.body.signIn.email.passwordRequired, false);
|
|
87
|
+
assert.strictEqual(patchCall.body.signIn.anonymous.enabled, true);
|
|
88
|
+
});
|
|
89
|
+
test('enables phone sign-in via PATCH', async () => {
|
|
90
|
+
const builder = new FirebaseAuthBuilder();
|
|
91
|
+
builder.phone();
|
|
92
|
+
await builder.deploy();
|
|
93
|
+
const patchCall = fetchCalls.find((c) => c.method === 'PATCH' && c.url.includes('/config'));
|
|
94
|
+
assert.ok(patchCall);
|
|
95
|
+
assert.ok(patchCall.url.includes('signIn.phoneNumber'));
|
|
96
|
+
assert.strictEqual(patchCall.body.signIn.phoneNumber.enabled, true);
|
|
97
|
+
});
|
|
98
|
+
test('configures OAuth provider via POST when IDP does not exist', async () => {
|
|
99
|
+
// getIdp returns 404 (not configured yet)
|
|
100
|
+
mockResponses['GET /defaultSupportedIdpConfigs/google.com'] = {
|
|
101
|
+
status: 404,
|
|
102
|
+
body: { error: { message: 'not found' } },
|
|
103
|
+
};
|
|
104
|
+
const builder = new FirebaseAuthBuilder();
|
|
105
|
+
builder.google({ clientId: 'client-id-123', clientSecret: 'secret-456' });
|
|
106
|
+
await builder.deploy();
|
|
107
|
+
const postCall = fetchCalls.find((c) => c.method === 'POST' && c.url.includes('defaultSupportedIdpConfigs'));
|
|
108
|
+
assert.ok(postCall);
|
|
109
|
+
assert.ok(postCall.url.includes('idpId=google.com'));
|
|
110
|
+
assert.strictEqual(postCall.body.clientId, 'client-id-123');
|
|
111
|
+
assert.strictEqual(postCall.body.clientSecret, 'secret-456');
|
|
112
|
+
assert.strictEqual(postCall.body.enabled, true);
|
|
113
|
+
});
|
|
114
|
+
test('updates OAuth provider via PATCH when IDP already exists', async () => {
|
|
115
|
+
mockResponses['GET /defaultSupportedIdpConfigs/github.com'] = {
|
|
116
|
+
status: 200,
|
|
117
|
+
body: { name: 'projects/my-project/defaultSupportedIdpConfigs/github.com', enabled: true },
|
|
118
|
+
};
|
|
119
|
+
const builder = new FirebaseAuthBuilder();
|
|
120
|
+
builder.github({ clientId: 'gh-id', clientSecret: 'gh-secret' });
|
|
121
|
+
await builder.deploy();
|
|
122
|
+
const patchCall = fetchCalls.find((c) => c.method === 'PATCH' && c.url.includes('defaultSupportedIdpConfigs/github.com'));
|
|
123
|
+
assert.ok(patchCall);
|
|
124
|
+
assert.strictEqual(patchCall.body.clientId, 'gh-id');
|
|
125
|
+
});
|
|
126
|
+
test('sets authorized domains via PATCH', async () => {
|
|
127
|
+
const builder = new FirebaseAuthBuilder();
|
|
128
|
+
builder.authorizedDomains(['example.com', 'app.example.com', 'localhost']);
|
|
129
|
+
await builder.deploy();
|
|
130
|
+
const patchCall = fetchCalls.find((c) => c.method === 'PATCH' && c.url.includes('/config'));
|
|
131
|
+
assert.ok(patchCall);
|
|
132
|
+
assert.ok(patchCall.url.includes('authorizedDomains'));
|
|
133
|
+
assert.deepStrictEqual(patchCall.body.authorizedDomains, [
|
|
134
|
+
'example.com',
|
|
135
|
+
'app.example.com',
|
|
136
|
+
'localhost',
|
|
137
|
+
]);
|
|
138
|
+
});
|
|
139
|
+
test('destroy returns without making API calls', async () => {
|
|
140
|
+
const builder = new FirebaseAuthBuilder();
|
|
141
|
+
const result = await builder.destroy();
|
|
142
|
+
assert.deepStrictEqual(result, { destroyed: 'auth' });
|
|
143
|
+
assert.strictEqual(fetchCalls.filter((c) => c.method !== 'GET').length, 0);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { test, describe, beforeEach, afterEach, mock } from 'node:test';
|
|
2
2
|
import assert from 'node:assert';
|
|
3
3
|
import fs from 'node:fs';
|
|
4
|
+
import { createHash } from 'node:crypto';
|
|
5
|
+
import { gzipSync } from 'node:zlib';
|
|
4
6
|
import { GoogleAuth } from 'google-auth-library';
|
|
5
7
|
import { FirebaseHostingBuilder } from './hosting.js';
|
|
6
8
|
import { Config } from '../../core/config.js';
|
|
@@ -129,6 +131,7 @@ describe('FirebaseHostingBuilder Unit Tests', () => {
|
|
|
129
131
|
assert.strictEqual(writeCalls.length, 0);
|
|
130
132
|
});
|
|
131
133
|
test('deploys new version and creates release when site exists', async () => {
|
|
134
|
+
const expectedHash = createHash('sha256').update(gzipSync(Buffer.from('hello from index.html'))).digest('hex');
|
|
132
135
|
mockResponses['GET /projects/my-project/sites/my-site'] = {
|
|
133
136
|
status: 200,
|
|
134
137
|
body: { name: 'projects/my-project/sites/my-site' }
|
|
@@ -141,10 +144,10 @@ describe('FirebaseHostingBuilder Unit Tests', () => {
|
|
|
141
144
|
status: 200,
|
|
142
145
|
body: {
|
|
143
146
|
uploadUrl: 'https://upload-firebasehosting.googleapis.com/upload/v111',
|
|
144
|
-
uploadRequiredHashes: [
|
|
147
|
+
uploadRequiredHashes: [expectedHash]
|
|
145
148
|
}
|
|
146
149
|
};
|
|
147
|
-
mockResponses[
|
|
150
|
+
mockResponses[`POST /upload/v111/${expectedHash}`] = {
|
|
148
151
|
status: 200,
|
|
149
152
|
body: {}
|
|
150
153
|
};
|
|
@@ -166,10 +169,8 @@ describe('FirebaseHostingBuilder Unit Tests', () => {
|
|
|
166
169
|
assert.ok(createVersionCall);
|
|
167
170
|
const populateCall = fetchCalls.find(c => c.method === 'POST' && c.url.endsWith('/versions/v111:populateFiles'));
|
|
168
171
|
assert.ok(populateCall);
|
|
169
|
-
assert.deepStrictEqual(populateCall.body.files, {
|
|
170
|
-
|
|
171
|
-
});
|
|
172
|
-
const uploadCall = fetchCalls.find(c => c.method === 'POST' && c.url.includes('/upload/v111/mock-hash-index'));
|
|
172
|
+
assert.deepStrictEqual(populateCall.body.files, { '/index.html': expectedHash });
|
|
173
|
+
const uploadCall = fetchCalls.find(c => c.method === 'POST' && c.url.includes(`/upload/v111/${expectedHash}`));
|
|
173
174
|
assert.ok(uploadCall);
|
|
174
175
|
assert.strictEqual(uploadCall.headers?.['Authorization'], 'Bearer fake-access-token');
|
|
175
176
|
const patchVersionCall = fetchCalls.find(c => c.method === 'PATCH' && c.url.endsWith('/versions/v111'));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { test, describe, beforeEach, afterEach, mock } from 'node:test';
|
|
2
|
+
import assert from 'node:assert';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { GoogleAuth } from 'google-auth-library';
|
|
7
|
+
import { FirebaseStorageBuilder } from './storage.js';
|
|
8
|
+
import { Config } from '../../core/config.js';
|
|
9
|
+
describe('FirebaseStorageBuilder Unit Tests', () => {
|
|
10
|
+
let originalFetch;
|
|
11
|
+
let fetchCalls = [];
|
|
12
|
+
let mockResponses = {};
|
|
13
|
+
// Real temp file: storage.ts uses a named ESM import that bypasses mock.method on the fs object
|
|
14
|
+
const tmpRulesFile = path.join(os.tmpdir(), 'puls-storage-test.rules');
|
|
15
|
+
function matchResponse(method, url) {
|
|
16
|
+
const key = Object.keys(mockResponses)
|
|
17
|
+
.filter((k) => {
|
|
18
|
+
const [m, path] = k.split(' ');
|
|
19
|
+
return method === m && url.includes(path);
|
|
20
|
+
})
|
|
21
|
+
.sort((a, b) => b.split(' ')[1].length - a.split(' ')[1].length)[0];
|
|
22
|
+
return key ? mockResponses[key] : null;
|
|
23
|
+
}
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
Config.set({
|
|
26
|
+
dryRun: false,
|
|
27
|
+
providers: { firebase: { projectId: 'my-project', serviceAccountPath: '/fake/sa.json' } },
|
|
28
|
+
});
|
|
29
|
+
originalFetch = globalThis.fetch;
|
|
30
|
+
fetchCalls = [];
|
|
31
|
+
mockResponses = {};
|
|
32
|
+
globalThis.fetch = async (input, init) => {
|
|
33
|
+
const url = String(input);
|
|
34
|
+
const method = init?.method ?? 'GET';
|
|
35
|
+
let body;
|
|
36
|
+
if (init?.body && typeof init.body === 'string') {
|
|
37
|
+
try {
|
|
38
|
+
body = JSON.parse(init.body);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
body = init.body;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
fetchCalls.push({ url, method, body });
|
|
45
|
+
const resp = matchResponse(method, url);
|
|
46
|
+
if (resp) {
|
|
47
|
+
return {
|
|
48
|
+
ok: resp.status >= 200 && resp.status < 300,
|
|
49
|
+
status: resp.status,
|
|
50
|
+
json: async () => resp.body,
|
|
51
|
+
text: async () => JSON.stringify(resp.body),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
ok: true, status: 200, json: async () => ({}), text: async () => '{}',
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
mock.method(GoogleAuth.prototype, 'getClient', async () => ({
|
|
59
|
+
getAccessToken: async () => ({ token: 'fake-token' }),
|
|
60
|
+
}));
|
|
61
|
+
fs.writeFileSync(tmpRulesFile, 'service firebase.storage { match /b/{b}/o/{a=**} { allow read; } }');
|
|
62
|
+
});
|
|
63
|
+
afterEach(() => {
|
|
64
|
+
globalThis.fetch = originalFetch;
|
|
65
|
+
mock.restoreAll();
|
|
66
|
+
try {
|
|
67
|
+
fs.unlinkSync(tmpRulesFile);
|
|
68
|
+
}
|
|
69
|
+
catch { /* ignore */ }
|
|
70
|
+
});
|
|
71
|
+
test('deploys without writing anything when no rules, cors, or lifecycle configured', async () => {
|
|
72
|
+
const builder = new FirebaseStorageBuilder('my-project.appspot.com');
|
|
73
|
+
const result = await builder.deploy();
|
|
74
|
+
assert.ok(result);
|
|
75
|
+
assert.strictEqual(result.bucket, 'my-project.appspot.com');
|
|
76
|
+
assert.strictEqual(result.project, 'my-project');
|
|
77
|
+
const writeCalls = fetchCalls.filter((c) => c.method !== 'GET');
|
|
78
|
+
assert.strictEqual(writeCalls.length, 0);
|
|
79
|
+
});
|
|
80
|
+
test('deploys storage rules by creating a ruleset and updating the release', async () => {
|
|
81
|
+
mockResponses['POST /rulesets'] = {
|
|
82
|
+
status: 200,
|
|
83
|
+
body: { name: 'projects/my-project/rulesets/ruleset-abc' },
|
|
84
|
+
};
|
|
85
|
+
mockResponses['PUT /releases/firebase.storage/my-project.appspot.com'] = {
|
|
86
|
+
status: 200,
|
|
87
|
+
body: {},
|
|
88
|
+
};
|
|
89
|
+
const builder = new FirebaseStorageBuilder('my-project.appspot.com');
|
|
90
|
+
builder.rules(tmpRulesFile);
|
|
91
|
+
await builder.deploy();
|
|
92
|
+
const rulesetCall = fetchCalls.find((c) => c.method === 'POST' && c.url.includes('/rulesets'));
|
|
93
|
+
assert.ok(rulesetCall);
|
|
94
|
+
assert.ok(rulesetCall.body.source.files[0].name === 'storage.rules');
|
|
95
|
+
const releaseCall = fetchCalls.find((c) => c.method === 'PUT' && c.url.includes('/releases/'));
|
|
96
|
+
assert.ok(releaseCall);
|
|
97
|
+
assert.ok(releaseCall.body.rulesetName.includes('ruleset-abc'));
|
|
98
|
+
});
|
|
99
|
+
test('deploys CORS configuration via PATCH to GCS API', async () => {
|
|
100
|
+
const builder = new FirebaseStorageBuilder('my-bucket');
|
|
101
|
+
builder.cors([
|
|
102
|
+
{ origin: ['https://example.com'], method: ['GET', 'PUT'], maxAge: 7200 },
|
|
103
|
+
]);
|
|
104
|
+
await builder.deploy();
|
|
105
|
+
const corsCall = fetchCalls.find((c) => c.method === 'PATCH' && c.url.includes('/b/my-bucket'));
|
|
106
|
+
assert.ok(corsCall);
|
|
107
|
+
assert.ok(Array.isArray(corsCall.body.cors));
|
|
108
|
+
assert.strictEqual(corsCall.body.cors[0].maxAgeSeconds, 7200);
|
|
109
|
+
assert.deepStrictEqual(corsCall.body.cors[0].origin, ['https://example.com']);
|
|
110
|
+
});
|
|
111
|
+
test('deploys lifecycle policy via PATCH to GCS API', async () => {
|
|
112
|
+
const builder = new FirebaseStorageBuilder('my-bucket');
|
|
113
|
+
builder.lifecycle({ deleteAfterDays: 30, matchesPrefix: ['tmp/', 'cache/'] });
|
|
114
|
+
await builder.deploy();
|
|
115
|
+
const lcCall = fetchCalls.find((c) => c.method === 'PATCH' && c.url.includes('/b/my-bucket'));
|
|
116
|
+
assert.ok(lcCall);
|
|
117
|
+
const rule = lcCall.body.lifecycle.rule[0];
|
|
118
|
+
assert.strictEqual(rule.action.type, 'Delete');
|
|
119
|
+
assert.strictEqual(rule.condition.age, 30);
|
|
120
|
+
assert.deepStrictEqual(rule.condition.matchesPrefix, ['tmp/', 'cache/']);
|
|
121
|
+
});
|
|
122
|
+
test('performs dry-run without any write API calls', async () => {
|
|
123
|
+
Config.set({
|
|
124
|
+
dryRun: true,
|
|
125
|
+
providers: { firebase: { projectId: 'my-project', serviceAccountPath: '/fake/sa.json' } },
|
|
126
|
+
});
|
|
127
|
+
const builder = new FirebaseStorageBuilder('my-project.appspot.com');
|
|
128
|
+
builder
|
|
129
|
+
.cors([{ origin: ['*'], method: ['GET'] }])
|
|
130
|
+
.lifecycle({ deleteAfterDays: 90 });
|
|
131
|
+
await builder.deploy();
|
|
132
|
+
const writeCalls = fetchCalls.filter((c) => c.method !== 'GET');
|
|
133
|
+
assert.strictEqual(writeCalls.length, 0);
|
|
134
|
+
});
|
|
135
|
+
test('uses project-derived default bucket name when none provided', async () => {
|
|
136
|
+
const builder = new FirebaseStorageBuilder();
|
|
137
|
+
const result = await builder.deploy();
|
|
138
|
+
// Default bucket is projectId.appspot.com
|
|
139
|
+
assert.strictEqual(result.bucket, 'my-project.appspot.com');
|
|
140
|
+
});
|
|
141
|
+
test('destroy returns without making write API calls', async () => {
|
|
142
|
+
const builder = new FirebaseStorageBuilder('my-project.appspot.com');
|
|
143
|
+
const result = await builder.destroy();
|
|
144
|
+
assert.ok(result.destroyed.includes('appspot.com'));
|
|
145
|
+
const writeCalls = fetchCalls.filter((c) => c.method !== 'GET');
|
|
146
|
+
assert.strictEqual(writeCalls.length, 0);
|
|
147
|
+
});
|
|
148
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "puls-dev",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.7",
|
|
4
4
|
"description": "Intent-driven infrastructure-as-code with eager discovery and no state files.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -30,6 +30,10 @@
|
|
|
30
30
|
"./gcp": {
|
|
31
31
|
"types": "./dist/providers/gcp/index.d.ts",
|
|
32
32
|
"default": "./dist/providers/gcp/index.js"
|
|
33
|
+
},
|
|
34
|
+
"./cloudflare": {
|
|
35
|
+
"types": "./dist/providers/cloudflare/index.d.ts",
|
|
36
|
+
"default": "./dist/providers/cloudflare/index.js"
|
|
33
37
|
}
|
|
34
38
|
},
|
|
35
39
|
"files": [
|
|
@@ -44,7 +48,7 @@
|
|
|
44
48
|
"build": "tsc",
|
|
45
49
|
"postbuild": "node -e \"const fs=require('fs'),f='dist/bin/puls.js',c=fs.readFileSync(f,'utf8');if(!c.startsWith('#!'))fs.writeFileSync(f,'#!/usr/bin/env node\\n'+c);\" && chmod +x dist/bin/puls.js",
|
|
46
50
|
"prepublishOnly": "npm run build",
|
|
47
|
-
"test": "tsx --test
|
|
51
|
+
"test": "find src -name '*.test.ts' | xargs tsx --test --test-concurrency=1"
|
|
48
52
|
},
|
|
49
53
|
"keywords": [
|
|
50
54
|
"iac",
|