@staticforge/azure 0.0.0-stage → 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/LICENSE +21 -0
- package/README.md +6 -2
- package/dist/.tsbuildinfo +1 -0
- package/dist/clients.d.ts +23 -0
- package/dist/clients.d.ts.map +1 -0
- package/dist/clients.js +85 -0
- package/dist/clients.js.map +1 -0
- package/dist/deployer.d.ts +20 -0
- package/dist/deployer.d.ts.map +1 -0
- package/dist/deployer.js +343 -0
- package/dist/deployer.js.map +1 -0
- package/dist/history.d.ts +37 -0
- package/dist/history.d.ts.map +1 -0
- package/dist/history.js +49 -0
- package/dist/history.js.map +1 -0
- package/dist/iac.d.ts +27 -0
- package/dist/iac.d.ts.map +1 -0
- package/dist/iac.js +188 -0
- package/dist/iac.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/rebuild.d.ts +15 -0
- package/dist/rebuild.d.ts.map +1 -0
- package/dist/rebuild.js +97 -0
- package/dist/rebuild.js.map +1 -0
- package/dist/swa-config.d.ts +19 -0
- package/dist/swa-config.d.ts.map +1 -0
- package/dist/swa-config.js +136 -0
- package/dist/swa-config.js.map +1 -0
- package/dist/uploader.d.ts +24 -0
- package/dist/uploader.d.ts.map +1 -0
- package/dist/uploader.js +58 -0
- package/dist/uploader.js.map +1 -0
- package/package.json +52 -3
- package/terraform/modules/site/main.tf +38 -0
- package/terraform/modules/site/outputs.tf +27 -0
- package/terraform/modules/site/variables.tf +44 -0
- package/terraform/modules/site/versions.tf +10 -0
- package/terraform/state-bootstrap/main.tf +83 -0
- package/terraform/state-bootstrap/outputs.tf +21 -0
- package/terraform/state-bootstrap/variables.tf +53 -0
- package/terraform/state-bootstrap/versions.tf +14 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"iac.d.ts","sourceRoot":"","sources":["../src/iac.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,iBAAiB,EAA0B,MAAM,mBAAmB,CAAC;AACnF,OAAO,EAEL,KAAK,UAAU,EAKhB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,KAAK,YAAY,EAA0C,MAAM,cAAc,CAAC;AAMzF,eAAO,MAAM,eAAe,QAA+B,CAAC;AAC5D,eAAO,MAAM,mBAAmB,QAAkC,CAAC;AAKnE,0CAA0C;AAC1C,eAAO,MAAM,aAAa,UAAgE,CAAC;AAI3F,MAAM,WAAW,aAAa;IAC5B,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;CACb;AAED,2GAA2G;AAC3G,wBAAgB,aAAa,CAC3B,MAAM,EAAE,iBAAiB,EACzB,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,GACjB,aAAa,CAUf;AAED,yEAAyE;AACzE,eAAO,MAAM,eAAe,GAAI,SAAS,MAAM,EAAE,YAAY,MAAM,KAAG,MACpB,CAAC;AAEnD;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,YAAY,EACrB,MAAM,EAAE,iBAAiB,EACzB,GAAG,EAAE,MAAM,CAAC,UAAU,GACrB,OAAO,CAAC,MAAM,CAAC,CAYjB;AAED,mEAAmE;AACnE,wBAAsB,cAAc,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAMzE;AAqED,wBAAgB,qBAAqB,CAAC,OAAO,GAAE,YAA+B,GAAG,UAAU,CA+F1F"}
|
package/dist/iac.js
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import { ErrorCode, SforgeError } from '@staticforge/core';
|
|
4
|
+
import { projectName, } from '@staticforge/iac';
|
|
5
|
+
import { azureError, defaultClients, httpStatus } from './clients.js';
|
|
6
|
+
/** Packaged HCL, next to src/ and dist/. */
|
|
7
|
+
const terraformDir = (path) => fileURLToPath(new URL(`../terraform/${path}`, import.meta.url));
|
|
8
|
+
export const SITE_MODULE_DIR = terraformDir('modules/site');
|
|
9
|
+
export const STATE_BOOTSTRAP_DIR = terraformDir('state-bootstrap');
|
|
10
|
+
const AZURERM_PROVIDER = { source: 'hashicorp/azurerm', version: '>= 4.0, < 5.0' };
|
|
11
|
+
const TIME_PROVIDER = { source: 'hashicorp/time', version: '>= 0.12, < 1.0' };
|
|
12
|
+
/** Where Azure offers Static Web Apps. */
|
|
13
|
+
export const SWA_LOCATIONS = ['westus2', 'centralus', 'eastus2', 'westeurope', 'eastasia'];
|
|
14
|
+
const MANAGEMENT_SCOPE = 'https://management.azure.com/.default';
|
|
15
|
+
/** `sfstate<hash of the subscription>` in resource group `sforge-state`, key `<site>/<target>.tfstate`. */
|
|
16
|
+
export function stateLocation(target, subscriptionId, project, targetName) {
|
|
17
|
+
return {
|
|
18
|
+
subscriptionId,
|
|
19
|
+
resourceGroup: target.state.resourceGroup ?? 'sforge-state',
|
|
20
|
+
storageAccount: target.state.storageAccount ??
|
|
21
|
+
`sfstate${createHash('sha256').update(subscriptionId.toLowerCase()).digest('hex').slice(0, 12)}`,
|
|
22
|
+
container: target.state.container,
|
|
23
|
+
key: `${project}/${targetName}.tfstate`,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/** Blob that holds a target's deploy history, in the state container. */
|
|
27
|
+
export const historyBlobName = (project, targetName) => `_sforge/${project}/${targetName}/versions.json`;
|
|
28
|
+
/**
|
|
29
|
+
* `targets.<n>.subscriptionId`, else AZURE_SUBSCRIPTION_ID / ARM_SUBSCRIPTION_ID,
|
|
30
|
+
* else the subscription `az login` selected.
|
|
31
|
+
*/
|
|
32
|
+
export async function resolveSubscription(clients, target, env) {
|
|
33
|
+
const id = target.subscriptionId ??
|
|
34
|
+
env.AZURE_SUBSCRIPTION_ID ??
|
|
35
|
+
env.ARM_SUBSCRIPTION_ID ??
|
|
36
|
+
(await clients.az(['account', 'show', '--query', 'id', '-o', 'tsv']));
|
|
37
|
+
if (id === undefined || id === '') {
|
|
38
|
+
throw new SforgeError(ErrorCode.CloudAuth, 'Could not tell which Azure subscription to use', {
|
|
39
|
+
hint: 'Run `az login`, or set targets.<name>.subscriptionId or AZURE_SUBSCRIPTION_ID.',
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
return id;
|
|
43
|
+
}
|
|
44
|
+
/** Fail early, with a useful message, when no credential works. */
|
|
45
|
+
export async function assertSignedIn(clients) {
|
|
46
|
+
try {
|
|
47
|
+
await clients.credential().getToken(MANAGEMENT_SCOPE);
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
throw azureError(error, 'Could not sign in to Azure', ErrorCode.CloudAuth);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const NOT_FOUND = new Set(['ResourceNotFound', 'ResourceGroupNotFound', 'ContainerNotFound']);
|
|
54
|
+
const isNotFound = (error) => httpStatus(error) === 404 || NOT_FOUND.has(error?.code ?? '');
|
|
55
|
+
/** Whether the state storage account and container exist. */
|
|
56
|
+
async function stateExists(clients, location) {
|
|
57
|
+
const storage = clients.storage(location.subscriptionId);
|
|
58
|
+
try {
|
|
59
|
+
await storage.storageAccounts.getProperties(location.resourceGroup, location.storageAccount);
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
if (isNotFound(error))
|
|
63
|
+
return false;
|
|
64
|
+
throw azureError(error, `Could not check the state storage account ${location.storageAccount}`, ErrorCode.CloudAuth);
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
await storage.blobContainers.get(location.resourceGroup, location.storageAccount, location.container);
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
if (isNotFound(error)) {
|
|
71
|
+
throw new SforgeError(ErrorCode.StateMissing, `The storage account ${location.storageAccount} exists but has no container "${location.container}"`, {
|
|
72
|
+
hint: 'Create the container, or point targets.<name>.state at another storage account.',
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
throw azureError(error, 'Could not check the state container', ErrorCode.CloudAuth);
|
|
76
|
+
}
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
function azurermBackend(location, key = location.key) {
|
|
80
|
+
return {
|
|
81
|
+
type: 'azurerm',
|
|
82
|
+
config: {
|
|
83
|
+
subscription_id: location.subscriptionId,
|
|
84
|
+
resource_group_name: location.resourceGroup,
|
|
85
|
+
storage_account_name: location.storageAccount,
|
|
86
|
+
container_name: location.container,
|
|
87
|
+
key,
|
|
88
|
+
// Entra ID (the az login or the CI identity), never account keys.
|
|
89
|
+
use_azuread_auth: true,
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/** Resource providers the modules use, registered by the azurerm provider itself. */
|
|
94
|
+
const RESOURCE_PROVIDERS = ['Microsoft.Web', 'Microsoft.Storage'];
|
|
95
|
+
const azurermProvider = (subscriptionId) => ({
|
|
96
|
+
features: {},
|
|
97
|
+
subscription_id: subscriptionId,
|
|
98
|
+
// Storage is managed through the management plane: account keys are disabled.
|
|
99
|
+
storage_use_azuread: true,
|
|
100
|
+
// Register only what the modules need (the default "core" set registers dozens).
|
|
101
|
+
resource_provider_registrations: 'none',
|
|
102
|
+
resource_providers_to_register: RESOURCE_PROVIDERS,
|
|
103
|
+
});
|
|
104
|
+
export function createAzureIacProfile(clients = defaultClients()) {
|
|
105
|
+
return {
|
|
106
|
+
provider: 'azure',
|
|
107
|
+
async prepare(input) {
|
|
108
|
+
const target = input.target;
|
|
109
|
+
const name = input.targetName;
|
|
110
|
+
for (const [option, label] of [
|
|
111
|
+
[target.domain, 'domain'],
|
|
112
|
+
[target.dnsZone, 'dnsZone'],
|
|
113
|
+
[target.ci, 'ci'],
|
|
114
|
+
]) {
|
|
115
|
+
if (option !== undefined) {
|
|
116
|
+
throw new SforgeError(ErrorCode.Unsupported, `targets.${name}.${label} is not supported for Azure targets yet`, {
|
|
117
|
+
hint: 'Custom domains and the GitHub Actions identity for Azure arrive in milestone M9.',
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (!SWA_LOCATIONS.includes(target.location)) {
|
|
122
|
+
throw new SforgeError(ErrorCode.ConfigInvalid, `targets.${name}.location "${target.location}" has no Static Web Apps`, { hint: `Use one of: ${SWA_LOCATIONS.join(', ')}.` });
|
|
123
|
+
}
|
|
124
|
+
const project = projectName(input.config);
|
|
125
|
+
const outputsOnly = input.purpose === 'outputs';
|
|
126
|
+
const subscriptionId = await resolveSubscription(clients, target, input.env);
|
|
127
|
+
if (!outputsOnly)
|
|
128
|
+
await assertSignedIn(clients);
|
|
129
|
+
const location = stateLocation(target, subscriptionId, project, name);
|
|
130
|
+
const exists = outputsOnly ? true : await stateExists(clients, location);
|
|
131
|
+
const tags = { 'sforge:managed-by': 'staticforge' };
|
|
132
|
+
const spec = {
|
|
133
|
+
requiredProviders: { azurerm: AZURERM_PROVIDER },
|
|
134
|
+
providers: { azurerm: [azurermProvider(subscriptionId)] },
|
|
135
|
+
module: {
|
|
136
|
+
name: 'site',
|
|
137
|
+
dir: SITE_MODULE_DIR,
|
|
138
|
+
inputs: {
|
|
139
|
+
name: project,
|
|
140
|
+
target: name,
|
|
141
|
+
resource_group_name: target.resourceGroup,
|
|
142
|
+
create_resource_group: target.createResourceGroup,
|
|
143
|
+
location: target.location,
|
|
144
|
+
sku: target.sku,
|
|
145
|
+
tags: { ...tags, 'sforge:site': project, 'sforge:target': name },
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
outputs: [
|
|
149
|
+
'static_web_app_name',
|
|
150
|
+
'static_web_app_id',
|
|
151
|
+
'resource_group',
|
|
152
|
+
'location',
|
|
153
|
+
'sku',
|
|
154
|
+
'default_host_name',
|
|
155
|
+
'url',
|
|
156
|
+
],
|
|
157
|
+
backend: azurermBackend(location),
|
|
158
|
+
};
|
|
159
|
+
const bootstrap = {
|
|
160
|
+
requiredProviders: { azurerm: AZURERM_PROVIDER, time: TIME_PROVIDER },
|
|
161
|
+
providers: { azurerm: [azurermProvider(subscriptionId)], time: [{}] },
|
|
162
|
+
module: {
|
|
163
|
+
name: 'state',
|
|
164
|
+
dir: STATE_BOOTSTRAP_DIR,
|
|
165
|
+
inputs: {
|
|
166
|
+
resource_group_name: location.resourceGroup,
|
|
167
|
+
location: target.state.location ?? target.location,
|
|
168
|
+
storage_account_name: location.storageAccount,
|
|
169
|
+
container_name: location.container,
|
|
170
|
+
tags,
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
outputs: ['resource_group', 'storage_account', 'storage_account_id', 'container'],
|
|
174
|
+
backend: azurermBackend(location, '_sforge/state-bootstrap.tfstate'),
|
|
175
|
+
};
|
|
176
|
+
return {
|
|
177
|
+
spec,
|
|
178
|
+
state: {
|
|
179
|
+
id: `azure-${subscriptionId.slice(0, 8)}-${location.storageAccount}`,
|
|
180
|
+
description: `azure://${location.storageAccount}/${location.container}/${location.key}`,
|
|
181
|
+
exists,
|
|
182
|
+
bootstrap,
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
//# sourceMappingURL=iac.js.map
|
package/dist/iac.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"iac.js","sourceRoot":"","sources":["../src/iac.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Provider } from '@staticforge/provider-api';
|
|
2
|
+
import { type AzureClients } from './clients.js';
|
|
3
|
+
export * from './clients.js';
|
|
4
|
+
export * from './deployer.js';
|
|
5
|
+
export * from './history.js';
|
|
6
|
+
export * from './iac.js';
|
|
7
|
+
export * from './rebuild.js';
|
|
8
|
+
export * from './swa-config.js';
|
|
9
|
+
export * from './uploader.js';
|
|
10
|
+
/** The Azure provider: Static Web Apps, named preview environments, rollback by rebuild. */
|
|
11
|
+
export declare function createAzureProvider(clients?: AzureClients): Provider;
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAC1D,OAAO,EAAE,KAAK,YAAY,EAAkB,MAAM,cAAc,CAAC;AAIjE,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAE9B,4FAA4F;AAC5F,wBAAgB,mBAAmB,CAAC,OAAO,GAAE,YAA+B,GAAG,QAAQ,CAOtF"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { defaultClients } from './clients.js';
|
|
2
|
+
import { REQUIRED_OUTPUTS, createAzureDeployer } from './deployer.js';
|
|
3
|
+
import { createAzureIacProfile } from './iac.js';
|
|
4
|
+
export * from './clients.js';
|
|
5
|
+
export * from './deployer.js';
|
|
6
|
+
export * from './history.js';
|
|
7
|
+
export * from './iac.js';
|
|
8
|
+
export * from './rebuild.js';
|
|
9
|
+
export * from './swa-config.js';
|
|
10
|
+
export * from './uploader.js';
|
|
11
|
+
/** The Azure provider: Static Web Apps, named preview environments, rollback by rebuild. */
|
|
12
|
+
export function createAzureProvider(clients = defaultClients()) {
|
|
13
|
+
return {
|
|
14
|
+
id: 'azure',
|
|
15
|
+
iac: createAzureIacProfile(clients),
|
|
16
|
+
createDeployer: (context) => createAzureDeployer(context, clients),
|
|
17
|
+
requiredOutputs: REQUIRED_OUTPUTS,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAqB,cAAc,EAAE,MAAM,cAAc,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACtE,OAAO,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AAEjD,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAE9B,4FAA4F;AAC5F,MAAM,UAAU,mBAAmB,CAAC,UAAwB,cAAc,EAAE;IAC1E,OAAO;QACL,EAAE,EAAE,OAAO;QACX,GAAG,EAAE,qBAAqB,CAAC,OAAO,CAAC;QACnC,cAAc,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC;QAClE,eAAe,EAAE,gBAAgB;KAClC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { DeployerContext } from '@staticforge/provider-api';
|
|
2
|
+
export interface Rebuilt {
|
|
3
|
+
/** A finished build (with its manifest) of the commit. */
|
|
4
|
+
buildDir: string;
|
|
5
|
+
cleanup(): void;
|
|
6
|
+
}
|
|
7
|
+
/** Builds the site as it was at a commit. Injectable, so tests need no git history. */
|
|
8
|
+
export type Rebuild = (sha: string, context: DeployerContext) => Promise<Rebuilt>;
|
|
9
|
+
/**
|
|
10
|
+
* Check the commit out into a temporary worktree next to the real one, build it
|
|
11
|
+
* with that commit's own config and content, and hand over the output. The
|
|
12
|
+
* project's dependencies and image cache are shared, so nothing is installed.
|
|
13
|
+
*/
|
|
14
|
+
export declare const rebuildAtCommit: Rebuild;
|
|
15
|
+
//# sourceMappingURL=rebuild.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rebuild.d.ts","sourceRoot":"","sources":["../src/rebuild.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAEjE,MAAM,WAAW,OAAO;IACtB,0DAA0D;IAC1D,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,uFAAuF;AACvF,MAAM,MAAM,OAAO,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;AAclF;;;;GAIG;AACH,eAAO,MAAM,eAAe,EAAE,OAwE7B,CAAC"}
|
package/dist/rebuild.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import { existsSync, mkdirSync, mkdtempSync, rmSync, symlinkSync } from 'node:fs';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join, relative } from 'node:path';
|
|
5
|
+
import { buildSite } from '@staticforge/build';
|
|
6
|
+
import { ErrorCode, SforgeError } from '@staticforge/core';
|
|
7
|
+
function git(cwd, args) {
|
|
8
|
+
return execFileSync('git', args, { cwd, stdio: ['ignore', 'pipe', 'pipe'] })
|
|
9
|
+
.toString()
|
|
10
|
+
.trim();
|
|
11
|
+
}
|
|
12
|
+
function unavailable(sha, reason) {
|
|
13
|
+
return new SforgeError(ErrorCode.Unsupported, `Cannot rebuild ${sha}: ${reason}`, {
|
|
14
|
+
hint: 'Azure rollbacks rebuild the site from git. Fetch the full history (in CI: fetch-depth: 0) or deploy a fixed version instead.',
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Check the commit out into a temporary worktree next to the real one, build it
|
|
19
|
+
* with that commit's own config and content, and hand over the output. The
|
|
20
|
+
* project's dependencies and image cache are shared, so nothing is installed.
|
|
21
|
+
*/
|
|
22
|
+
export const rebuildAtCommit = async (sha, context) => {
|
|
23
|
+
const { rootDir } = context;
|
|
24
|
+
let top;
|
|
25
|
+
try {
|
|
26
|
+
top = git(rootDir, ['rev-parse', '--show-toplevel']);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
throw unavailable(sha, 'the project is not in a git repository');
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
git(top, ['cat-file', '-e', `${sha}^{commit}`]);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
throw unavailable(sha, 'the commit is not in this repository');
|
|
36
|
+
}
|
|
37
|
+
const scratch = mkdtempSync(join(tmpdir(), 'sforge-rollback-'));
|
|
38
|
+
const worktree = join(scratch, 'checkout');
|
|
39
|
+
const cleanup = () => {
|
|
40
|
+
try {
|
|
41
|
+
git(top, ['worktree', 'remove', '--force', worktree]);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// Already gone, or never added.
|
|
45
|
+
}
|
|
46
|
+
rmSync(scratch, { recursive: true, force: true });
|
|
47
|
+
try {
|
|
48
|
+
git(top, ['worktree', 'prune']);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// Not worth failing a rollback over.
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
try {
|
|
55
|
+
git(top, ['worktree', 'add', '--detach', worktree, sha]);
|
|
56
|
+
const projectDir = join(worktree, relative(top, rootDir));
|
|
57
|
+
// Config files may import packages; share the project's node_modules.
|
|
58
|
+
for (const [from, to] of [
|
|
59
|
+
[join(top, 'node_modules'), join(worktree, 'node_modules')],
|
|
60
|
+
[join(rootDir, 'node_modules'), join(projectDir, 'node_modules')],
|
|
61
|
+
[join(rootDir, '.sforge', 'cache'), join(projectDir, '.sforge', 'cache')],
|
|
62
|
+
]) {
|
|
63
|
+
if (existsSync(from) && !existsSync(to)) {
|
|
64
|
+
mkdirSync(join(to, '..'), { recursive: true });
|
|
65
|
+
symlinkSync(from, to, 'dir');
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (context.loadConfig === undefined) {
|
|
69
|
+
throw new SforgeError(ErrorCode.Internal, 'The provider was created without a config loader');
|
|
70
|
+
}
|
|
71
|
+
let loaded;
|
|
72
|
+
try {
|
|
73
|
+
loaded = await context.loadConfig(projectDir);
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
throw unavailable(sha, `its configuration cannot be loaded (${error.message})`);
|
|
77
|
+
}
|
|
78
|
+
context.logger.info(`Building ${sha} for the rollback`);
|
|
79
|
+
const build = await buildSite({
|
|
80
|
+
rootDir: loaded.rootDir,
|
|
81
|
+
config: loaded.config,
|
|
82
|
+
scratchDir: join(scratch, 'out'),
|
|
83
|
+
logger: context.logger,
|
|
84
|
+
});
|
|
85
|
+
if (!build.ok) {
|
|
86
|
+
throw new SforgeError(ErrorCode.DeployFailed, `Version ${sha} no longer builds`, {
|
|
87
|
+
details: { diagnostics: build.diagnostics.filter((d) => d.severity === 'error') },
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
return { buildDir: build.outDir, cleanup };
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
cleanup();
|
|
94
|
+
throw error;
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
//# sourceMappingURL=rebuild.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rebuild.js","sourceRoot":"","sources":["../src/rebuild.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { SforgeConfig } from '@staticforge/core';
|
|
2
|
+
import { type BuildManifest } from '@staticforge/provider-api';
|
|
3
|
+
/** Static Web Apps rejects a configuration file larger than 20 KB. */
|
|
4
|
+
export declare const SWA_CONFIG_LIMIT: number;
|
|
5
|
+
export declare const SWA_CONFIG_FILE = "staticwebapp.config.json";
|
|
6
|
+
export interface GeneratedSwaConfig {
|
|
7
|
+
config: Record<string, unknown>;
|
|
8
|
+
json: string;
|
|
9
|
+
/** Cache rules left out because the file would exceed the size limit (those files revalidate). */
|
|
10
|
+
droppedRoutes: number;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* The `staticwebapp.config.json` of a build: the `security` headers on every
|
|
14
|
+
* response, the cache classes of the manifest, the trailing-slash policy, a 404
|
|
15
|
+
* page and the search page's own CSP. Static Web Apps has no per-file metadata,
|
|
16
|
+
* so cache classes become route rules, within the 20 KB limit of the file.
|
|
17
|
+
*/
|
|
18
|
+
export declare function generateStaticWebAppConfig(config: SforgeConfig, manifest: BuildManifest): GeneratedSwaConfig;
|
|
19
|
+
//# sourceMappingURL=swa-config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"swa-config.d.ts","sourceRoot":"","sources":["../src/swa-config.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EACL,KAAK,aAAa,EAMnB,MAAM,2BAA2B,CAAC;AAEnC,sEAAsE;AACtE,eAAO,MAAM,gBAAgB,QAAY,CAAC;AAI1C,eAAO,MAAM,eAAe,6BAA6B,CAAC;AAO1D,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,kGAAkG;IAClG,aAAa,EAAE,MAAM,CAAC;CACvB;AA6DD;;;;;GAKG;AACH,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,YAAY,EACpB,QAAQ,EAAE,aAAa,GACtB,kBAAkB,CAsEpB"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { dirname } from 'node:path';
|
|
2
|
+
import { cacheControl, contentType, publishedFiles, searchPaths, securityHeaders, } from '@staticforge/provider-api';
|
|
3
|
+
/** Static Web Apps rejects a configuration file larger than 20 KB. */
|
|
4
|
+
export const SWA_CONFIG_LIMIT = 20 * 1024;
|
|
5
|
+
/** Stay below the limit: the platform may count differently than JSON.stringify. */
|
|
6
|
+
const BUDGET = 19 * 1024;
|
|
7
|
+
export const SWA_CONFIG_FILE = 'staticwebapp.config.json';
|
|
8
|
+
/** Extensions Static Web Apps may not know, given the type `sforge preview` serves them as. */
|
|
9
|
+
const MIME_CANDIDATES = ['.avif', '.webp', '.vtt', '.webmanifest', '.mjs', '.wasm', '.xml'];
|
|
10
|
+
function mimeTypes(manifest) {
|
|
11
|
+
const present = new Set(publishedFiles(manifest)
|
|
12
|
+
.map(([path]) => /\.[a-z0-9]+$/i.exec(path)?.[0].toLowerCase())
|
|
13
|
+
.filter((ext) => ext !== undefined));
|
|
14
|
+
return Object.fromEntries(MIME_CANDIDATES.filter((ext) => present.has(ext)).map((ext) => [
|
|
15
|
+
ext,
|
|
16
|
+
contentType(`x${ext}`).split(';')[0],
|
|
17
|
+
]));
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Routes that cache fingerprinted files for a year. A directory whose files are
|
|
21
|
+
* all immutable (the image variants) becomes one wildcard route, anything else
|
|
22
|
+
* is listed file by file, most useful first.
|
|
23
|
+
*/
|
|
24
|
+
function immutableRoutes(manifest) {
|
|
25
|
+
const files = publishedFiles(manifest);
|
|
26
|
+
const totals = new Map();
|
|
27
|
+
for (const [path, entry] of files) {
|
|
28
|
+
for (let dir = dirname(path); dir !== '.'; dir = dirname(dir)) {
|
|
29
|
+
const total = totals.get(dir) ?? { all: 0, immutable: 0 };
|
|
30
|
+
total.all++;
|
|
31
|
+
if (entry.cache === 'immutable')
|
|
32
|
+
total.immutable++;
|
|
33
|
+
totals.set(dir, total);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const dirs = new Set();
|
|
37
|
+
const exact = [];
|
|
38
|
+
for (const [path, entry] of files) {
|
|
39
|
+
if (entry.cache !== 'immutable')
|
|
40
|
+
continue;
|
|
41
|
+
// The shallowest ancestor holding nothing but immutable files.
|
|
42
|
+
const ancestors = [];
|
|
43
|
+
for (let dir = dirname(path); dir !== '.'; dir = dirname(dir))
|
|
44
|
+
ancestors.unshift(dir);
|
|
45
|
+
const whole = ancestors.find((dir) => {
|
|
46
|
+
const total = totals.get(dir);
|
|
47
|
+
return total !== undefined && total.all === total.immutable;
|
|
48
|
+
});
|
|
49
|
+
if (whole !== undefined)
|
|
50
|
+
dirs.add(whole);
|
|
51
|
+
else
|
|
52
|
+
exact.push(path);
|
|
53
|
+
}
|
|
54
|
+
return { dirs: [...dirs].sort(), files: exact.sort() };
|
|
55
|
+
}
|
|
56
|
+
/** The `Strict-Transport-Security` value for the configured policy. */
|
|
57
|
+
function hstsValue(hsts) {
|
|
58
|
+
return [
|
|
59
|
+
`max-age=${hsts.maxAge}`,
|
|
60
|
+
...(hsts.includeSubDomains ? ['includeSubDomains'] : []),
|
|
61
|
+
...(hsts.preload ? ['preload'] : []),
|
|
62
|
+
].join('; ');
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The `staticwebapp.config.json` of a build: the `security` headers on every
|
|
66
|
+
* response, the cache classes of the manifest, the trailing-slash policy, a 404
|
|
67
|
+
* page and the search page's own CSP. Static Web Apps has no per-file metadata,
|
|
68
|
+
* so cache classes become route rules, within the 20 KB limit of the file.
|
|
69
|
+
*/
|
|
70
|
+
export function generateStaticWebAppConfig(config, manifest) {
|
|
71
|
+
const security = securityHeaders(config);
|
|
72
|
+
const globalHeaders = {
|
|
73
|
+
'Cache-Control': cacheControl('revalidate'),
|
|
74
|
+
'X-Content-Type-Options': 'nosniff',
|
|
75
|
+
'Referrer-Policy': security.referrerPolicy,
|
|
76
|
+
...(security.contentSecurityPolicy !== null && {
|
|
77
|
+
'Content-Security-Policy': security.contentSecurityPolicy,
|
|
78
|
+
}),
|
|
79
|
+
...(security.hsts !== null && { 'Strict-Transport-Security': hstsValue(security.hsts) }),
|
|
80
|
+
...(security.frameOptions !== null && { 'X-Frame-Options': security.frameOptions }),
|
|
81
|
+
...(security.permissionsPolicy !== null && {
|
|
82
|
+
'Permissions-Policy': security.permissionsPolicy,
|
|
83
|
+
}),
|
|
84
|
+
};
|
|
85
|
+
const routes = [];
|
|
86
|
+
if (security.searchContentSecurityPolicy !== null &&
|
|
87
|
+
security.searchContentSecurityPolicy !== security.contentSecurityPolicy) {
|
|
88
|
+
const headers = { 'Content-Security-Policy': security.searchContentSecurityPolicy };
|
|
89
|
+
for (const pattern of searchPaths(config)) {
|
|
90
|
+
// `/x/*` does not match `/x`, which the search page is served at. Static Web Apps
|
|
91
|
+
// treats `/x` and `/x/` as the same route and rejects a configuration with both.
|
|
92
|
+
if (pattern.endsWith('/*'))
|
|
93
|
+
routes.push({ route: pattern.slice(0, -2), headers });
|
|
94
|
+
routes.push({ route: pattern, headers });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const base = {
|
|
98
|
+
// SWA's "always" also appends a slash to files (style.css → style.css/); "auto" adds it to
|
|
99
|
+
// folders only, which is what build.trailingSlash "always" means.
|
|
100
|
+
...(config.build.cleanUrls && {
|
|
101
|
+
trailingSlash: config.build.trailingSlash === 'always' ? 'auto' : 'never',
|
|
102
|
+
}),
|
|
103
|
+
...(manifest.files['404.html'] !== undefined && {
|
|
104
|
+
responseOverrides: { '404': { rewrite: '/404.html', statusCode: 404 } },
|
|
105
|
+
}),
|
|
106
|
+
globalHeaders,
|
|
107
|
+
...(Object.keys(mimeTypes(manifest)).length > 0 && { mimeTypes: mimeTypes(manifest) }),
|
|
108
|
+
};
|
|
109
|
+
const immutable = immutableRoutes(manifest);
|
|
110
|
+
const immutableHeaders = { 'Cache-Control': cacheControl('immutable') };
|
|
111
|
+
const wanted = [
|
|
112
|
+
...immutable.dirs.map((dir) => ({ route: `/${dir}/*`, headers: immutableHeaders })),
|
|
113
|
+
...immutable.files.map((path) => ({ route: `/${path}`, headers: immutableHeaders })),
|
|
114
|
+
];
|
|
115
|
+
const render = (extra, space) => JSON.stringify({ ...base, routes: [...routes, ...extra] }, null, space);
|
|
116
|
+
// The size only grows with the number of routes: find how many fit. Directory rules
|
|
117
|
+
// come first (they cover the most files), so single files are dropped from the end.
|
|
118
|
+
let low = 0;
|
|
119
|
+
let high = wanted.length;
|
|
120
|
+
while (low < high) {
|
|
121
|
+
const mid = Math.ceil((low + high) / 2);
|
|
122
|
+
if (render(wanted.slice(0, mid)).length <= BUDGET)
|
|
123
|
+
low = mid;
|
|
124
|
+
else
|
|
125
|
+
high = mid - 1;
|
|
126
|
+
}
|
|
127
|
+
const used = wanted.slice(0, low);
|
|
128
|
+
const pretty = render(used, 2);
|
|
129
|
+
const json = pretty.length <= BUDGET ? pretty : render(used);
|
|
130
|
+
return {
|
|
131
|
+
config: JSON.parse(json),
|
|
132
|
+
json: `${json}\n`,
|
|
133
|
+
droppedRoutes: wanted.length - low,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=swa-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"swa-config.js","sourceRoot":"","sources":["../src/swa-config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface UploadRequest {
|
|
2
|
+
/** Directory of the files to publish (already without dot-paths). */
|
|
3
|
+
dir: string;
|
|
4
|
+
/** `production`, or the name of a preview environment. */
|
|
5
|
+
environment: string;
|
|
6
|
+
/** Deployment token of the Static Web App. Never logged. */
|
|
7
|
+
token: string;
|
|
8
|
+
env: NodeJS.ProcessEnv;
|
|
9
|
+
}
|
|
10
|
+
export interface UploadResult {
|
|
11
|
+
/** The URL the deployment client reports, when it does. */
|
|
12
|
+
url?: string;
|
|
13
|
+
}
|
|
14
|
+
/** Sends a directory to a Static Web App environment. */
|
|
15
|
+
export type Uploader = (request: UploadRequest) => Promise<UploadResult>;
|
|
16
|
+
/** Remove ANSI colors and the token from output that is shown to users. */
|
|
17
|
+
export declare function sanitize(output: string, token: string): string;
|
|
18
|
+
/**
|
|
19
|
+
* Uploads with `swa deploy`, which downloads and runs Microsoft's
|
|
20
|
+
* StaticSitesClient. The token travels in the environment, and the process runs
|
|
21
|
+
* in an empty directory so it cannot pick up a project's `.github/workflows`.
|
|
22
|
+
*/
|
|
23
|
+
export declare const swaCliUploader: Uploader;
|
|
24
|
+
//# sourceMappingURL=uploader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"uploader.d.ts","sourceRoot":"","sources":["../src/uploader.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,aAAa;IAC5B,qEAAqE;IACrE,GAAG,EAAE,MAAM,CAAC;IACZ,0DAA0D;IAC1D,WAAW,EAAE,MAAM,CAAC;IACpB,4DAA4D;IAC5D,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;CACxB;AAED,MAAM,WAAW,YAAY;IAC3B,2DAA2D;IAC3D,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,yDAAyD;AACzD,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,EAAE,aAAa,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;AAYzE,2EAA2E;AAC3E,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAQ9D;AAED;;;;GAIG;AACH,eAAO,MAAM,cAAc,EAAE,QAoC5B,CAAC"}
|
package/dist/uploader.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { mkdtempSync, readFileSync, rmSync } from 'node:fs';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { dirname, join } from 'node:path';
|
|
5
|
+
import { createRequire } from 'node:module';
|
|
6
|
+
import { ErrorCode, SforgeError } from '@staticforge/core';
|
|
7
|
+
const TIMEOUT_MS = 15 * 60_000;
|
|
8
|
+
/** Entry point of the `swa` command line shipped as a dependency. */
|
|
9
|
+
function swaBinary() {
|
|
10
|
+
const require = createRequire(import.meta.url);
|
|
11
|
+
const manifestPath = require.resolve('@azure/static-web-apps-cli/package.json');
|
|
12
|
+
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
13
|
+
return join(dirname(manifestPath), manifest.bin.swa);
|
|
14
|
+
}
|
|
15
|
+
/** Remove ANSI colors and the token from output that is shown to users. */
|
|
16
|
+
export function sanitize(output, token) {
|
|
17
|
+
return (output
|
|
18
|
+
.replaceAll(token, '***')
|
|
19
|
+
// eslint-disable-next-line no-control-regex
|
|
20
|
+
.replace(/\x1b\[[0-9;]*m/g, '')
|
|
21
|
+
.trim());
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Uploads with `swa deploy`, which downloads and runs Microsoft's
|
|
25
|
+
* StaticSitesClient. The token travels in the environment, and the process runs
|
|
26
|
+
* in an empty directory so it cannot pick up a project's `.github/workflows`.
|
|
27
|
+
*/
|
|
28
|
+
export const swaCliUploader = async ({ dir, environment, token, env }) => {
|
|
29
|
+
const cwd = mkdtempSync(join(tmpdir(), 'sforge-swa-'));
|
|
30
|
+
try {
|
|
31
|
+
const output = await new Promise((resolve, reject) => {
|
|
32
|
+
const child = spawn(process.execPath, [swaBinary(), 'deploy', dir, '--env', environment, '--no-use-keychain'], {
|
|
33
|
+
cwd,
|
|
34
|
+
env: { ...env, SWA_CLI_DEPLOYMENT_TOKEN: token, FORCE_COLOR: '0', CI: 'true' },
|
|
35
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
36
|
+
timeout: TIMEOUT_MS,
|
|
37
|
+
});
|
|
38
|
+
let text = '';
|
|
39
|
+
child.stdout.on('data', (chunk) => (text += String(chunk)));
|
|
40
|
+
child.stderr.on('data', (chunk) => (text += String(chunk)));
|
|
41
|
+
child.on('error', reject);
|
|
42
|
+
child.on('close', (code) => resolve({ code, text }));
|
|
43
|
+
});
|
|
44
|
+
const clean = sanitize(output.text, token);
|
|
45
|
+
if (output.code !== 0 || /deployment failed|deployment failure/i.test(clean)) {
|
|
46
|
+
throw new SforgeError(ErrorCode.DeployFailed, `The Static Web Apps deployment client failed (exit code ${output.code})`, {
|
|
47
|
+
details: { output: clean.split('\n').slice(-30).join('\n') },
|
|
48
|
+
hint: 'Run again with --verbose for more output. The client is downloaded on first use: check that you have network access.',
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
const url = /Project deployed to\s+(https:\/\/\S+)/.exec(clean)?.[1];
|
|
52
|
+
return url === undefined ? {} : { url };
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
rmSync(cwd, { recursive: true, force: true });
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
//# sourceMappingURL=uploader.js.map
|